Commits

prokhin_alexey committed 85d8531

Software detection

Comments (0)

Files changed (99)

share/icons/apps/adium.png

Added
New image

share/icons/apps/agile.png

Added
New image

share/icons/apps/anastasia.png

Added
New image

share/icons/apps/apple.png

Added
New image

share/icons/apps/bitlbee.png

Added
New image

share/icons/apps/bombus-mod.png

Added
New image

share/icons/apps/bombus-ng.png

Added
New image

share/icons/apps/bombus-p.png

Added
New image

share/icons/apps/bombus-pl.png

Added
New image

share/icons/apps/bombus.png

Added
New image

share/icons/apps/centerim.png

Added
New image

share/icons/apps/climm.png

Added
New image

share/icons/apps/coccinella.png

Added
New image

share/icons/apps/colibry.png

Added
New image

share/icons/apps/dichat.png

Added
New image

share/icons/apps/digsby.png

Added
New image

share/icons/apps/emacs.png

Added
New image

share/icons/apps/exodus.png

Added
New image

share/icons/apps/gaim.png

Added
New image

share/icons/apps/gajim.png

Added
New image

share/icons/apps/glicq.png

Added
New image

share/icons/apps/gtalk-android.png

Added
New image

share/icons/apps/gtalk.png

Added
New image

share/icons/apps/icq-2000.png

Added
New image

share/icons/apps/icq-2001.png

Added
New image

share/icons/apps/icq-2003.png

Added
New image

share/icons/apps/icq-2003pro.png

Added
New image

share/icons/apps/icq-2go.png

Added
New image

share/icons/apps/icq-4lite.png

Added
New image

share/icons/apps/icq-50.png

Added
New image

share/icons/apps/icq-51.png

Added
New image

share/icons/apps/icq-6.png

Added
New image

share/icons/apps/icq-60.png

Added
New image

share/icons/apps/icq-l4.png

Added
New image

share/icons/apps/icq-l5.png

Added
New image

share/icons/apps/icq-mac.png

Added
New image

share/icons/apps/icq.png

Added
New image

share/icons/apps/icqbot.png

Added
New image

share/icons/apps/im2.png

Added
New image

share/icons/apps/imgate.png

Added
New image

share/icons/apps/implus.png

Added
New image

share/icons/apps/inlux.png

Added
New image

share/icons/apps/jabbim.png

Added
New image

share/icons/apps/jajc.png

Added
New image

share/icons/apps/jicq.png

Added
New image

share/icons/apps/jimm-corepager.png

Added
New image

share/icons/apps/jimm.png

Added
New image

share/icons/apps/kopete.png

Added
New image

share/icons/apps/kxicq.png

Added
New image

share/icons/apps/licq.png

Added
New image

share/icons/apps/mcabber.png

Added
New image

share/icons/apps/mchat.png

Added
New image

share/icons/apps/meebo.png

Added
New image

share/icons/apps/micq.png

Added
New image

share/icons/apps/mip.png

Added
New image

share/icons/apps/miranda-hotcoffee.png

Added
New image

share/icons/apps/miranda.png

Added
New image

share/icons/apps/mrim.png

Added
New image

share/icons/apps/naticq.png

Added
New image

share/icons/apps/pidgin.png

Added
New image

share/icons/apps/pigeon.png

Added
New image

share/icons/apps/psi+.png

Added
New image

share/icons/apps/psi.png

Added
New image

share/icons/apps/pyicq.png

Added
New image

share/icons/apps/qip-2005.png

Added
New image

share/icons/apps/qip-infium.png

Added
New image

share/icons/apps/qip-pda.png

Added
New image

share/icons/apps/qip-symbian.png

Added
New image

share/icons/apps/qip.png

Added
New image

share/icons/apps/qipinf.png

Added
New image

share/icons/apps/qutim-k8.png

Added
New image

share/icons/apps/rnq.png

Added
New image

share/icons/apps/rq.png

Added
New image

share/icons/apps/siejc.png

Added
New image

share/icons/apps/sim.png

Added
New image

share/icons/apps/slick.png

Added
New image

share/icons/apps/smaper.png

Added
New image

share/icons/apps/sticq.png

Added
New image

share/icons/apps/talkonaut.png

Added
New image

share/icons/apps/tkabber.png

Added
New image

share/icons/apps/trillian.png

Added
New image

share/icons/apps/unknown-client.png

Added
New image

share/icons/apps/unknown.png

Added
New image

share/icons/apps/vacuum.png

Added
New image

share/icons/apps/vmicq.png

Added
New image

share/icons/apps/webicq.png

Added
New image

share/icons/apps/yachat.png

Added
New image

share/icons/apps/yapp.png

Added
New image

src/lime/account.cpp

 		cfg.setValue("name", contact->name());
 		cfg.setValue("groups", contact->groups());
 		cfg.setValue("avatar", contact->avatar());
+		cfg.beginGroup("extendedInfo");
+		auto extInfos = contact->allExtendedInfo();
+		auto itr = extInfos.constBegin();
+		for (auto end = extInfos.constEnd(); itr != end; ++itr) {
+			if (itr->value("storeToConfig", false).toBool())
+				cfg.setValue(itr.key(), itr.value());
+		}
+		cfg.endGroup();
 		cfg.endGroup();
 	}
 }
 		contact->updateName(cfg.value("name", QString()));
 		contact->updateGroups(cfg.value("groups", QStringList()));
 		contact->updateAvatar(cfg.value("avatar", QString()));
+		cfg.beginGroup("extendedInfo");
+		cfg.handleValuesAndGroup([=] (const QString &key, const QVariant &value) {
+			contact->setExtendedInfo(key, value.toMap());
+		});
+		cfg.endGroup();
 		contact->updateInList(true);
 	});
 }

src/lime/buddy.cpp

 			statusText = statusName();
 		tip->addField(statusText, QString(), this->statusIconName(),
 					  ToolTip::IconBeforeTitle, ToolTip::Top + 800);
+
+		auto client = m_extendedInfo.find("client");
+		if (client != m_extendedInfo.end()) {
+			QString title = m_status & Online ?
+					tr("Possible client") :
+					tr("Last client");
+			QString desc = client->value("description").toString();
+			QIcon icon = client->value("icon").value<QIcon>();
+			tip->addField(title, desc, icon, ToolTip::IconBeforeDescription, ToolTip::Bottom - 2000);
+		}
 	};
 }
 

src/lime/config.cpp

 	}
 }
 
+void Config::handleValuesAndGroup(const function<void(const QString &key, const QVariant &value)> &handler)
+{
+	auto &level = d->levels.last();
+	Q_ASSERT(level.type == Level::Map);
+	auto itr = level.map->begin();
+	auto end = level.map->end();
+	for (; itr != end; ++itr)
+		handler(itr.key(), itr.value());
+}
+
 void Config::setValue(const QString &fullName, const QVariant &value)
 {
 	auto groups = getGroupList(fullName);

src/lime/config.h

 	void handleArray(const function<void()> &handler);
 	void handleGroups(const function<void()> &handler);
 	void handleValues(const function<void(const QString &key, const QVariant &value)> &handler);
+	void handleValuesAndGroup(const function<void(const QString &key, const QVariant &value)> &handler);
 
 	void setValue(const QString &name, const QVariant &value);
 	QVariant value(const QString &name, const QVariant &def = QVariant()) const;

src/protocols/jabber/jabberaccount.cpp

 #include "jabberbookmarks.h"
 #include "jabberservicediscovery.h"
 #include "jabbervcards.h"
+#include "jabbersoftwaredetection.h"
 #include "servicebrowser/servicebrowser.h"
 #include "xmlconsole/xmlconsole.h"
 #include <jreen/connectionbosh.h>
 	return conf;
 }
 
+ChatUnit *JabberAccount::unit(const Jreen::JID &jid)
+{
+	if (auto contact = this->contact(jid.bare(), false)) {
+		if (jid.isFull())
+			return contact->resource(jid.resource());
+		return contact;
+	} else if (auto conf = conference(jid.bare(), false)) {
+		if (jid.isFull())
+			return conf->participant(jid.resource());
+		return conf;
+	}
+	return 0;
+}
+
+JabberResourceInterface *JabberAccount::resource(const Jreen::JID &jid)
+{
+	if (auto contact = this->contact(jid.bare(), false))
+		return contact->resource(jid.resource());
+	else if (auto conf = conference(jid.bare(), false))
+		return conf->participant(jid.resource());
+	return 0;
+}
+
 void JabberAccount::openXmlConsole()
 {
 	if (m_xmlConsole) {
 	m_bookmarksManager = new JabberBookmarks(this);
 	m_serviceDiscovery = new JabberServiceDiscovery(this);
 	m_vcardManager = new JabberVCardManager(this);
+	Q_UNUSED(new JabberSoftwareDetection(this));
 	m_client->presence().addExtension(new Jreen::VCardUpdate());
 
 	auto caps = m_client->presence().payload<Jreen::Capabilities>();

src/protocols/jabber/jabberaccount.h

 class JabberServiceDiscovery;
 class JabberServiceBrowser;
 class JabberVCardManager;
+class JabberResourceInterface;
+class ChatUnit;
+class JabberResource;
 class XmlConsole;
 
 class JabberAccount : public Account
 	JabberVCardManager *vcardManager() const { return m_vcardManager; }
 	JabberContact *contact(const QString &id, bool create = false);
 	JabberConference *conference(const QString &id, bool create = false);
+	ChatUnit *unit(const Jreen::JID &jid);
+	JabberResourceInterface *resource(const Jreen::JID &jid);
 	QList<JabberContact*> contacts();
 	void showServiceBrowser();
 	Jreen::JID jid() const { return m_client->jid(); }

src/protocols/jabber/jabberconference.h

 
 class JabberAccount;
 
-class JabberConfParticipant : public ConferenceParticipant
+class JabberConfParticipant : public ConferenceParticipant, public JabberResourceInterface
 {
 public:
     JabberConfParticipant(const QString &id, const QString &nick, Conference *conference);
 	void updateAvatar(const QString &avatar) { ConferenceParticipant::updateAvatar(avatar); }
 protected:
 	virtual SendMessageError sendMessageImpl(const Lime::Message &message);
+	Buddy *toBuddy() { return this; }
 private:
 	friend class JabberConference;
 	Jreen::MUCRoom::Affiliation m_affiliation;

src/protocols/jabber/jabbercontact.h

 	void handleChatState(ChatState state) { ChatUnit::handleChatState(state); }
 	void handleMessageReceipt(int id, MessageReceipt success) { ChatUnit::handleMessageReceipt(id, success); }
 	void updateAvatar(const QString &avatar) { MetaContact::updateAvatar(avatar); }
+	JabberResource *resource(const QString &name, bool create = true);
 protected:
 	virtual SendMessageError sendMessageImpl(const Lime::Message& message);
 	void sendChatStateImpl(ChatState state);
 	friend class JabberRoster;
 	friend class JabberMessageFilter;
 	void updateData(QSharedPointer<Jreen::RosterItem> item);
-	JabberResource *resource(const QString &name, bool create = true);
 private:
 	QMap<QString, JabberResource*> m_resourceMap;
 };

src/protocols/jabber/jabberresource.h

 class JabberContact;
 class JabberAccount;
 
-class JabberResource : public Buddy
+class JabberResourceInterface
+{
+public:
+	void setFeatures(const QSet<QString> &features) { m_features = features; }
+	QSet<QString> features() { return m_features; }
+	virtual Buddy *toBuddy() = 0;
+private:
+	QSet<QString> m_features;
+};
+
+class JabberResource : public Buddy, public JabberResourceInterface
 {
 public:
 	JabberResource(JabberContact *contact, const QString &name, JabberAccount *account);
 	void handleChatState(ChatState state) { ChatUnit::handleChatState(state); }
 	JabberAccount *account() const { return reinterpret_cast<JabberAccount*>(Buddy::account()); }
 protected:
+	Buddy *toBuddy() { return this; }
 	virtual SendMessageError sendMessageImpl(const Lime::Message &message);
 	virtual void sendChatStateImpl(ChatUnit::ChatState state);
 };

src/protocols/jabber/jabbersoftwaredetection.cpp

+/****************************************************************************
+ *  jsoftwaredetection.cpp
+ *
+ *  Copyright (c) 2009 by Nigmatullin Ruslan <euroelessar@gmail.com>
+ *  Copyright (c) 2010 by Sidorov Aleksey <sauron@citadelspb.com>
+ *
+ ***************************************************************************
+ *                                                                         *
+ *   This library 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 file is part of Lime.
+    Copyright (C) 2011  Alexey Prokhin <alexey.prokhin@yandex.ru>
+
+    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 3 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 <http://www.gnu.org/licenses/>.
+*/
+
+#include "jabbersoftwaredetection.h"
+#include "jabberaccount.h"
+#include "jabberresource.h"
+#include <jreen/client.h>
+#include <jreen/disco.h>
+#include <jreen/dataform.h>
+#include <jreen/softwareversion.h>
+#include <jreen/capabilities.h>
+#include <jreen/iq.h>
+#include <jreen/error.h>
+#include <lime/config.h>
+#include <lime/icon.h>
+#include <QTimerEvent>
+#include <QUrl>
+
+namespace Lime
+{
+
+inline QString toConfigNode(QString node)
+{
+	return node.replace(QLatin1Char('/'), QLatin1String("%2F"));
+}
+
+inline QString fromConfigNode(QString node)
+{
+	return node.replace(QLatin1String("%2F"), QChar(QLatin1Char('/')));
+}
+	
+JabberSoftwareDetection::JabberSoftwareDetection(JabberAccount *account) : QObject(account)
+{
+	m_account = account;
+	Jreen::Client *client = account->client();
+	connect(client,SIGNAL(presenceReceived(Jreen::Presence)),SLOT(handlePresence(Jreen::Presence)));
+	
+	Config cache(QLatin1String("jabberhash"));
+	cache.beginGroup(QLatin1String("softwareInfo"));
+	foreach (const QString &node, cache.childGroups()) {
+		cache.beginGroup(node);
+		SoftwareInfo info;
+		info.features = QSet<QString>::fromList(cache.value(QLatin1String("features"), QStringList()));
+		info.name = cache.value(QLatin1String("name"), QString());
+		info.version = cache.value(QLatin1String("version"), QString());
+		info.os = cache.value(QLatin1String("os"), QString());
+		info.icon = getClientIcon(info.name);
+		info.description = getClientDescription(info.name, info.version, info.os);
+		info.finished = cache.value(QLatin1String("finished"), !info.name.isEmpty());
+		m_hash.insert(fromConfigNode(node), info);
+		cache.endGroup();
+	}
+}
+
+JabberSoftwareDetection::~JabberSoftwareDetection()
+{
+}
+
+// TODO: Move to some singletone
+void JabberSoftwareDetection::timerEvent(QTimerEvent *ev)
+{
+	if (ev->timerId() == m_timer.timerId()) {
+		m_timer.stop();
+		Config cache(QLatin1String("jabberhash"));
+		cache.beginGroup(QLatin1String("softwareInfo"));
+		for (int i = 0; i < m_recent.size(); i++) {
+			const SoftwareInfo info = m_hash.value(m_recent[i]);
+			cache.beginGroup(toConfigNode(m_recent[i]));
+			cache.setValue(QLatin1String("features"), QStringList(info.features.toList()));
+			cache.setValue(QLatin1String("name"), info.name);
+			cache.setValue(QLatin1String("version"), info.version);
+			cache.setValue(QLatin1String("os"), info.os);
+			cache.setValue(QLatin1String("finished"), info.finished);
+			cache.endGroup();
+		}
+		m_recent.clear();
+	} else {
+		QObject::timerEvent(ev);
+	}
+}
+
+void JabberSoftwareDetection::handlePresence(const Jreen::Presence &presence)
+{
+	if (auto resource = m_account->resource(presence.from())) {
+		auto buddy = resource->toBuddy();
+		if (!resource->features().isEmpty())
+			return;
+
+		QString node;
+		if (Jreen::Capabilities *caps = presence.payload<Jreen::Capabilities>().data()) {
+//			qDebug() << "handle caps" << caps->node();
+			QString capsNode = caps->node();
+			if(capsNode == QLatin1String("http://www.android.com/gtalk/client/caps")) {
+				QString software = "GTalk (Android)";
+				QString softwareVersion = caps->ver();
+				QString client = getClientDescription(software, softwareVersion, QString());
+				updateClientData(buddy, client, software, softwareVersion, QString(), "gtalk-android");
+				return;
+			}
+			static const QRegExp regExp("^http://.*google.com/.*client/caps$");
+			Q_ASSERT(regExp.isValid());
+			if(regExp.exactMatch(capsNode))	{
+				QString software = "GTalk";
+				if(capsNode.startsWith("http://mail."))
+					software += " (GMail)";
+				else if(capsNode.startsWith("http://talkgadget."))
+					software += " (Gadget)";
+				QString softwareVersion = caps->ver();
+				QString client = getClientDescription(software, softwareVersion, QString());
+				updateClientData(buddy, client, software, softwareVersion, QString(), "gtalk");
+				return;
+			} else {
+				node = caps->node() + '#' + caps->ver();
+				QString qNode = node;
+				buddy->setProperty("node", qNode);
+				SoftwareInfoHash::iterator it = m_hash.find(qNode);
+//				qDebug() << "find from hash" << m_hash.count();
+				if (it != m_hash.end()) {
+					SoftwareInfo &info = *it;
+					resource->setFeatures(info.features);
+//					qDebug() << info.name;
+					if (!info.finished) {
+//						qDebug() << "Send software version request";
+						Jreen::IQ iq(Jreen::IQ::Get, presence.from());
+						iq.addExtension(new Jreen::SoftwareVersion());
+						m_account->client()->send(iq,this,SLOT(handleIQ(Jreen::IQ,int)),RequestSoftware);
+					} else {
+						updateClientData(buddy, info.description, info.name, info.version, info.os, info.icon);
+					}
+					return;
+				}
+			}
+		}
+
+		setClientInfo(buddy, "", "unknown-client");
+		Jreen::IQ iq(Jreen::IQ::Get,presence.from());
+		iq.addExtension(new Jreen::Disco::Info(node));
+		m_account->client()->send(iq,this,SLOT(handleIQ(Jreen::IQ,int)),RequestDisco);
+	}
+}
+
+void JabberSoftwareDetection::handleIQ(const Jreen::IQ &iq, int context)
+{	
+	if (context == RequestSoftware) {
+		if (Jreen::Error::Ptr error = iq.error()) {
+			if (error->condition() != Jreen::Error::ServiceUnavailable)
+				return;
+			if (auto resource = m_account->resource(iq.from())) {
+				QString node = resource->toBuddy()->property("node").toString();
+				SoftwareInfoHash::iterator it = m_hash.find(node);
+				if (it != m_hash.end()) {
+					SoftwareInfo &info = (*it);
+					info.finished = true;
+					updateCache(node, info, true);
+				}
+			}
+			return;
+		}
+		if (Jreen::SoftwareVersion::Ptr soft = iq.payload<Jreen::SoftwareVersion>()) {
+			if (auto resource = m_account->resource(iq.from())) {
+				auto buddy = resource->toBuddy();
+				QString node = buddy->property("node").toString();
+				QString software = soft->name();
+				QString softwareVersion = soft->version();
+				QString os = soft->os();
+				QString icon = getClientIcon(software);;
+				QString client = getClientDescription(software, softwareVersion, os);
+				updateClientData(buddy, client, software, softwareVersion, os, icon);
+				SoftwareInfoHash::iterator it = m_hash.find(node);
+				if (it != m_hash.end()) {
+					SoftwareInfo &info = (*it);
+					info.finished = true;
+					info.name = software;
+					info.version = softwareVersion;
+//					info.os = os;
+					info.icon = icon;
+					info.description = client;
+					updateCache(node, info, true);
+				}
+			}
+		}
+	} else if(context == RequestDisco) {
+		Jreen::Disco::Info *discoInfo = iq.payload<Jreen::Disco::Info>().data();
+		if(!discoInfo)
+			return;
+		iq.accept();
+		QString node = discoInfo->node();
+
+		SoftwareInfo info;
+		info.features = discoInfo->features();
+
+		Jreen::DataForm::Ptr form = discoInfo->form();
+
+		if (form && form->typeName() == QLatin1String("urn:xmpp:dataforms:softwareinfo")) {
+			QString software = form->field(QLatin1String("software")).value();
+			QString softwareVersion = form->field(QLatin1String("software_version")).value();
+			QString os = form->field(QLatin1String("os")).value();
+			QString osVersion = form->field(QLatin1String("os_version")).value();
+			QString icon = getClientIcon(software);
+			QString client = getClientDescription(software, softwareVersion, os);
+
+			if (!software.isEmpty()) {
+				info.icon = icon;
+				info.name = software;
+				if (!softwareVersion.isEmpty())
+					info.version = softwareVersion;
+			}
+			if (!os.isEmpty()) {
+				info.os = os;
+				if (!osVersion.isEmpty())
+					info.os.append(" ").append(osVersion);
+			}
+			info.description = client;
+			info.finished = true;
+		} else {
+			foreach (auto &identity, discoInfo->identities()) {
+				if (identity.category == QLatin1String("client")) {
+					info.name = identity.name;
+					info.icon = getClientIcon(info.name);
+					info.description = getClientDescription(info.name, QString(), QString());
+					break;
+				}
+			}
+		}
+
+		updateCache(node, info);
+
+		if (auto resource = m_account->resource(iq.from())) {
+			auto buddy = resource->toBuddy();
+			if (buddy->property("node").isNull())
+				buddy->setProperty("node", node);
+
+			if (!info.finished) {
+				Jreen::IQ get(Jreen::IQ::Get,buddy->id());
+				get.addExtension(new Jreen::SoftwareVersion());
+				m_account->client()->send(get,this,SLOT(handleIQ(Jreen::IQ,int)),RequestSoftware);
+			} else {
+				updateClientData(buddy, info.description, info.name, info.version, info.os, info.icon);
+			}
+			resource->setFeatures(info.features);
+		}
+	}
+}
+
+void JabberSoftwareDetection::updateCache(const QString &node, const SoftwareInfo &info, bool fixed)
+{
+	if (node.isEmpty())
+		return;
+	if (!fixed)
+		m_hash.insert(node, info);
+	m_recent << node;
+	if (!m_timer.isActive())
+		m_timer.start(5000, this);
+}
+
+void JabberSoftwareDetection::updateClientData(Buddy *resource, const QString &client,
+										  const QString &software, const QString &softwareVersion,
+										  const QString &os, const QString &icon)
+{
+	resource->setProperty("client", client);
+	resource->setProperty("software", software);
+	resource->setProperty("softwareVersion", softwareVersion);
+	resource->setProperty("os", os);
+	resource->setProperty("clientIcon", icon);
+	setClientInfo(resource, client, icon);
+}
+
+void JabberSoftwareDetection::setClientInfo(Buddy *resource, const QString &client, const QString &iconName)
+{
+	QVariantMap clientInfo;
+	QIcon icon = Icon(iconName);
+	clientInfo.insert("id", "client");
+	clientInfo.insert("title", tr("Possible client"));
+	clientInfo.insert("icon", QVariant::fromValue(icon));
+	clientInfo.insert("description", client);
+	clientInfo.insert("priority", 85);
+	clientInfo.insert("storeToConfig", true);
+	resource->setExtendedInfo("client", clientInfo);
+}
+
+QString JabberSoftwareDetection::getClientDescription(const QString &software, const QString &softwareVersion,
+												 const QString &os)
+{
+	Q_UNUSED(os);
+	QString desc = software;
+	if (!softwareVersion.isEmpty())
+		desc += " " + softwareVersion;
+	return desc;
+}
+
+template <typename Char, int N>
+bool isStrEqual(const QString &s1, const Char (&s2)[N])
+{
+	if (N != s1.size())
+		return false;
+	for (int i = 0; i < N; i++)
+		if (s1[i] != s2[i])
+			return false;
+	return true;
+}
+
+QString JabberSoftwareDetection::getClientIcon(const QString &software)
+{
+	if (software.isEmpty())
+		return QString();
+	if (software == QLatin1String("Miranda IM Jabber"))
+		return QLatin1String("miranda-jabber");
+	else if (software == QLatin1String("bombusmod"))
+		return QLatin1String("bombus-mod");
+	else if (software == QLatin1String("bombusqd"))
+		return QLatin1String("bombus-qd");
+	else if (software == QLatin1String("bombus.pl"))
+		return QLatin1String("bombus-pl");
+	else if (software == QLatin1String("bombus+"))
+		return QLatin1String("bombus-p");
+	else if (isStrEqual(software, L"Я.онлайн"))
+		return QLatin1String("yachat");
+	else if (software == QLatin1String("hotcoffee"))
+		return QLatin1String("miranda-hotcoffee");
+	else if (software == QLatin1String("jabber.el"))
+		return QLatin1String("emacs");
+	else if (software == QLatin1String("just another jabber client"))
+		return QLatin1String("jajc");
+	else if (isStrEqual(software, L"Пиджин")) // Stupid pidgin devels! Name mustn't be localized!
+		return QLatin1String("pidgin");
+	return (software.toLower().replace(' ', '-') += QLatin1String("-jabber"));
+}
+
+}

src/protocols/jabber/jabbersoftwaredetection.h

+/****************************************************************************
+ *  jsoftwaredetection.h
+ *
+ *  Copyright (c) 2009 by Nigmatullin Ruslan <euroelessar@gmail.com>
+ *
+ ***************************************************************************
+ *                                                                         *
+ *   This library 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 file is part of Lime.
+    Copyright (C) 2011  Alexey Prokhin <alexey.prokhin@yandex.ru>
+
+    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 3 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 <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef JSOFTWAREDETECTION_H
+#define JSOFTWAREDETECTION_H
+
+#include <QObject>
+#include <QSet>
+#include <QBasicTimer>
+#include <QStringList>
+
+
+namespace Jreen
+{
+class Presence;
+class IQ;
+class Disco;
+}
+
+namespace Lime
+{
+
+class Buddy;
+class ChatUnit;
+class JabberAccount;
+class JabberResourceInterface;
+
+class JabberSoftwareDetection : public QObject
+{
+	enum { RequestDisco, RequestSoftware };
+	Q_OBJECT
+public:
+	struct SoftwareInfo
+	{
+		SoftwareInfo() : finished(false) {}
+		QSet<QString> features;
+		QString name;
+		QString version;
+		QString os;
+		QString icon;
+		QString description;
+		bool finished;
+	};
+	typedef QHash<QString, SoftwareInfo> SoftwareInfoHash;
+
+	JabberSoftwareDetection(JabberAccount *account);
+	~JabberSoftwareDetection();
+	
+protected:
+	void timerEvent(QTimerEvent *ev);
+protected slots:
+	void handlePresence(const Jreen::Presence &presence);
+	void handleIQ(const Jreen::IQ &iq, int context);
+private:
+	void updateCache(const QString &node, const SoftwareInfo &info, bool fixed = false);
+	void updateClientData(Buddy *resource, const QString &client,
+						  const QString &software, const QString &softwareVersion,
+						  const QString &os, const QString &clientIcon);
+	void setClientInfo(Buddy *resource, const QString &client, const QString &clientIcon);
+	QString getClientDescription(const QString &software, const QString &softwareVersion, const QString &os);
+	QString getClientIcon(const QString &software);
+private:
+	JabberAccount *m_account;
+	QHash<QString, SoftwareInfo> m_hash;
+	QStringList m_recent;
+	QBasicTimer m_timer;
+};
+}
+
+#endif // JSOFTWAREDETECTION_H