diff options
Diffstat (limited to 'kopete/protocols/msn')
100 files changed, 21667 insertions, 0 deletions
diff --git a/kopete/protocols/msn/Changelog b/kopete/protocols/msn/Changelog new file mode 100644 index 00000000..80f52264 --- /dev/null +++ b/kopete/protocols/msn/Changelog @@ -0,0 +1,106 @@ +Have fun using this all-improved plugin and feel free to contribute patches +and other improvements to our mailing list! Although we all like to boast +about our great work, we're sure there are still bugs remaining, which is +why we don't call this release 1.0, but only 0.5. + +Nevertheless, we feel this new MSN plugin is an enormous step forward from +the last 0.4.1 release and we recommend anyone to try out this all-improved +plugin. Please read the release notes first before reporting bugs, but please +do report anything not listed there! + +Thanks for your interest in Kopete! + + October 2002, the Kopete team <kopete-devel@kde.org> + + +CHANGES IN THE MSN PLUGIN SINCE KOPETE 0.4.1 + +- Ported the plugin to the new MetaContact API, allowing a locally cached + copy of the contact list to be always available (even when offline) and + to combine your MSN contacts with other messaging systems in one entry + in the contact list. + +- Added additional online states ('be right back', 'out to lunch', 'busy', + 'invisible') and the possibility to connect directly with a particular + status (especially useful with 'invisible') + +- Fix multi-user chat now the API finally supports it properly + +- Fix a grave bug in Kopete 0.4.1 where Kopete would popup the 'new user' + dialog for every user in your block list, asking whether you want to + allow or block the user, often crashing Kopete completely + +- Fix support for Unicode messages + +- Fix the 'unhandled error 219' problem that caused Kopete to disconnect + unexpectedly for some people + +- Added possibility to talk with users who aren't in the contact list + +- Incoming filetransfers + +- As usual, several other bugfixes + +CHANGES IN THE MSN PLUGIN SINCE KOPETE 0.4 + +- Added block/unblock user + +- Don't show contacts from the allow list if they are not also in the + friend list (like deleted contacts). Small problem: there already was + a need to have a gui for manipulating blocked/allowed contacts, with + this change this is even a bit more urgent... + +- Hopefully fix a problem with an empty reverse list on a fresh MSN account. + can't test, because by the time the recompile was done the reverse list + was no longer empty... + +- Fix a problem with MSN users no longer receiving messages. Apparently + Microsoft changed the server so messages without an explicit font name are + no longer passed on. + +- Fixed UTF8 handling not really being UTF8. MSN should work fine now with + all unicode characters + +- Moved the plugin to use KGenericFactory as preparation for more KDE-style + plugin handling (as opposed to the current custom code) + +- Fixed crash when disconnecting while an earlier connect was still running + +- Made the connect code asynchronous, so connecting doesn't hang kopete + while processing + +- Fixed minor memory leak in the connect code + +CHANGES IN THE MSN PLUGIN SINCE KOPETE 0.3 + +Many things changed since 0.3. I won't mention them all, because so much of +the internal code changed that the individual commits often fix more than I +was even aware of at that time. Below are the bigger changes and fixes: + +- Ported the plugin to the new KopeteMessageManager. This move unifies the + handling of various resources like chat windows, balloons, system tray + flashing, and more. In Kopete 0.3 this was the exclusive domain of the + ICQ plugin, in this release all plugins except IRC already use the shared + code. + +- Rewrote almost all of the internal protocol handling, fixing an awful lot + of bugs during the process. The main goal was to make the code more + maintainable and extensible, but the gratuitous bug fixes are of course + much more useful for most people. The most important fix of all is a + grave bug that caused the plugin to read a fixed-size 1kb buffer in Kopete + 0.3 without checking for additional data, often causing the plugin to + seemingly 'hang'. + +- Added the ability to change the display name while connected. This can + currently only be done from the context menu. The option in the + preferences never worked, and still does not do what you'd expect it to + do. Sorry :) + +- Added much more useful debug code for developers, testers and other + interested people. It is also a lot *more* debug output, so if you're + scared of console output, better not start Kopete from it... + +- All those tiny bugfixes of which I don't even know whether they fix + regressions introduced during the development of version 0.4, or whether + they fix long-standing bugs. + diff --git a/kopete/protocols/msn/Makefile.am b/kopete/protocols/msn/Makefile.am new file mode 100644 index 00000000..ffecef3c --- /dev/null +++ b/kopete/protocols/msn/Makefile.am @@ -0,0 +1,46 @@ +if include_msn_webcam +WEBCAM = webcam +WEBCAM_LIBMINICWRAPPER = webcam/libmimicwrapper.la +endif + +KDE_OPTIONS = nofinal +METASOURCES = AUTO +SUBDIRS = ui $(WEBCAM) . icons config +AM_CPPFLAGS = -Iui \ + -I$(srcdir)/webcam \ + -I$(srcdir)/ui \ + $(KOPETE_INCLUDES) \ + $(all_includes) + +kde_module_LTLIBRARIES = kopete_msn.la +lib_LTLIBRARIES = libkopete_msn_shared.la + +CLEANFILES = dummy.cpp + +libkopete_msn_shared_la_SOURCES = msnprotocol.cpp msnaccount.cpp \ + msnaddcontactpage.cpp msncontact.cpp msnsocket.cpp msnchatsession.cpp msndebugrawcmddlg.cpp \ + msnnotifysocket.cpp msnswitchboardsocket.cpp msnfiletransfersocket.cpp msninvitation.cpp \ + sha1.cpp msnsecureloginhandler.cpp msnchallengehandler.cpp dispatcher.cpp \ + p2p.cpp messageformatter.cpp incomingtransfer.cpp outgoingtransfer.cpp \ + webcam.cpp + +libkopete_msn_shared_la_LIBADD = ./ui/libkopetemsnui.la ../../libkopete/libkopete.la $(WEBCAM_LIBMINICWRAPPER) ../../libkopete/avdevice/libkopete_videodevice.la $(LIB_KIO) +libkopete_msn_shared_la_LDFLAGS = -version-info 0:0:0 -no-undefined $(all_libraries) + +kopete_msn_la_SOURCES = dummy.cpp webcam.cpp +kopete_msn_la_LIBADD = libkopete_msn_shared.la + +kopete_msn_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) + +dummy.cpp: $(srcdir)/Makefile.am + echo '#include "kdemacros.h"' > $@ + echo 'extern "C" KDE_EXPORT void *init_libkopete_msn_shared();' >> $@ + echo 'extern "C" KDE_EXPORT void *init_kopete_msn() { return init_libkopete_msn_shared(); }' >> $@ + +service_DATA = kopete_msn.desktop +servicedir = $(kde_servicesdir) + +mydatadir = $(kde_datadir)/kopete_msn +mydata_DATA = msnchatui.rc +noinst_HEADERS = p2p.h dispatcher.h messageformatter.h incomingtransfer.h \ + outgoingtransfer.h webcam.h diff --git a/kopete/protocols/msn/ReleaseNotes b/kopete/protocols/msn/ReleaseNotes new file mode 100644 index 00000000..3a2b2f6a --- /dev/null +++ b/kopete/protocols/msn/ReleaseNotes @@ -0,0 +1,31 @@ +If you can bear with beta-quality code, we welcome you to try out this +highly improved plugin and use it as your primary instant messaging system! +Patches are always welcome on kopete-devel@kde.org. Trivial fixes can be +committed directly to KDE CVS if you have an account, but please coordinate +larger changes with us to avoid double work. + +Thanks for your interest in Kopete! + + October 2002, the Kopete team <kopete-devel@kde.org> + +RELEASE NOTES FOR THE MSN PLUGIN IN KOPETE 0.5 + +Most of the MSN plugin has seen an enormous improvement since the previous +release, with many bugs fixed in all aspects of the plugin, most notably +the contact list handling and group chats. See the Changelog for a more +detailed description of what was changed in this release. + +Some issues in MSN are still remaining though. We hope to fix them in the +next release, since we think they are not critical enough to delay this +release: + +- MSN allows you to have multiple groups with the same name, because + internally a group ID is used. Kopete currently uses the name as a unique + identifier, however, and will likely get a bit confused by this. If you + do experience problems, you could join both groups using another MSN + client, like the official client, Trillian or Gaim as a workaround. + +- Kopete contacts can be at Top-Level and in no groups. MSN doesn't + support this freature. The kopete's contact list can differe from server + if you have top-level contact + diff --git a/kopete/protocols/msn/TODO b/kopete/protocols/msn/TODO new file mode 100644 index 00000000..be73e830 --- /dev/null +++ b/kopete/protocols/msn/TODO @@ -0,0 +1,5 @@ +Some things to think about as of 2003/04/15 +(according to Gof) +Adding 'search for user' +Adding MSN chatroom protocol +Add MSN alerts diff --git a/kopete/protocols/msn/config/Makefile.am b/kopete/protocols/msn/config/Makefile.am new file mode 100644 index 00000000..ab29db48 --- /dev/null +++ b/kopete/protocols/msn/config/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) $(all_includes) + +kde_module_LTLIBRARIES = kcm_kopete_msn.la + +kcm_kopete_msn_la_SOURCES = msnprefs.ui msnpreferences.cpp +kcm_kopete_msn_la_LDFLAGS = -no-undefined -module $(KDE_PLUGIN) $(all_libraries) +kcm_kopete_msn_la_LIBADD = ../../../libkopete/libkopete.la $(LIB_KUTILS) + +service_DATA = kopete_msn_config.desktop +servicedir = $(kde_servicesdir)/kconfiguredialog + diff --git a/kopete/protocols/msn/config/kopete_msn_config.desktop b/kopete/protocols/msn/config/kopete_msn_config.desktop new file mode 100644 index 00000000..2af4da08 --- /dev/null +++ b/kopete/protocols/msn/config/kopete_msn_config.desktop @@ -0,0 +1,123 @@ +[Desktop Entry] +Icon=msn_protocol +Type=Service +ServiceTypes=KCModule + +X-KDE-ModuleType=Library +X-KDE-Library=kopete_msn +X-KDE-FactoryName=MSNProtocolConfigFactory +X-KDE-ParentApp=kopete_msn +X-KDE-ParentComponents=kopete_msn + +Name=MSN Plugin +Name[ar]=توصيلة MSN +Name[be]=Модуль MSN +Name[bg]=MSN +Name[bn]=এমএসএন প্লাগিন +Name[br]=Lugent MSN +Name[bs]=MSN dodatak +Name[ca]=Connector de MSN +Name[cs]=MSN modul +Name[cy]=Ategyn MSN +Name[da]=MSN-Plugin +Name[de]=MSN-Modul +Name[el]=Πρόσθετο MSN +Name[es]=Complemento de MSN +Name[et]=MSN plugin +Name[eu]=MSN Plugin-a +Name[fa]=وصلۀ اماسان +Name[fi]=MSN-liitännäinen +Name[fr]=Module MSN +Name[ga]=Breiseán MSN +Name[gl]=Plugin para MSN +Name[he]=תוסף MSN +Name[hi]=एमएसएन प्लगइन +Name[hr]=MSN umetak +Name[hu]=MSN modul +Name[is]=MSN íforrit +Name[it]=Plugin MSN +Name[ja]=MSN プラグイン +Name[ka]=MSN მოდული +Name[kk]=MSN плагин модулі +Name[km]=កម្មវិធីជំនួយ MSN +Name[lt]=MSN įskiepis +Name[mk]=MSN-приклучок +Name[nb]=MSN programtillegg +Name[nds]=MSN-Moduul +Name[ne]=एमएसएन प्लगइन +Name[nl]=MSN-plugin +Name[nn]=MSN-programtillegg +Name[pa]=MSN ਪਲੱਗਇਨ +Name[pl]=Wtyczka MSN +Name[pt]='Plugin' MSN +Name[pt_BR]=Plug-in MSN +Name[ro]=Modul MSN +Name[ru]=Модуль MSN +Name[se]=MSN-lassemoduvla +Name[sk]=Modul MSN +Name[sl]=Vstavek za MSN +Name[sr]=MSN прикључак +Name[sr@Latn]=MSN priključak +Name[sv]=MSN-insticksprogram +Name[ta]=MSN செருகல் +Name[tg]=Модули MSN +Name[tr]=MSN Eklentisi +Name[uk]=Втулок MSN +Name[uz]=MSN plagini +Name[uz@cyrillic]=MSN плагини +Name[wa]=Tchôke-divins MSN +Name[zh_CN]=MSN 插件 +Name[zh_HK]=MSN 插件 +Name[zh_TW]=MSN 外掛程式 +Comment=Microsoft Network Protocol +Comment[ar]=بروتوكول شبكة Microsoft +Comment[be]=Пратакол сеткі Microsoft Network +Comment[bg]=Протокол за връзка с Microsoft Network +Comment[bn]=মাইক্রোসফ্ট নেটওয়ার্ক প্রোটোকল +Comment[br]=Komenad rouedad Microsoft +Comment[bs]=Microsoft Network protokol +Comment[ca]=Protocol per a la xarxa de Microsoft +Comment[cs]=Protokol sítě Microsoft +Comment[cy]=Protocol Rhwydwaith Microsoft +Comment[de]=Microsoft Netzwerk-Protokoll +Comment[el]=Πρωτόκολλο Microsoft Network +Comment[es]=Protocolo de red de Microsoft +Comment[et]=Microsofti võrguprotokoll +Comment[fa]=قرارداد شبکۀ میکروسافت +Comment[fi]=Microsoft Network -yhteyskäytäntö +Comment[fr]=Protocole réseau Microsoft +Comment[ga]=Prótacal Gréasáin Mhicrosoft +Comment[gl]=Protocolo para a rede de Microsoft +Comment[he]=תוסף חיבור לרשת מיקרוסופט +Comment[hi]=माइक्रोसॉफ्ट नेटवर्क प्रोटोकॉल +Comment[hr]=Microsoft mrežni protokol +Comment[hu]=Microsoft hálózati protokoll +Comment[is]=Microsoft Network Protocol +Comment[it]=Protocollo di rete Microsoft +Comment[ja]=Microsoft ネットワークプロトコル +Comment[ka]=Microsoft ქსელის ოქმი +Comment[kk]=Microsoft Network желі протоколы +Comment[km]=ពិធីការបណ្ដាញម៉ៃក្រូសូហ្វ +Comment[lt]=Microsoft tinklo protokolas +Comment[mk]=Мрежен протокол на Microsoft +Comment[nds]=Microsoft-Nettwarkprotokoll +Comment[ne]=माइक्रोसफ्ट सञ्जाल प्रोटोकल +Comment[nl]=Protocol voor Microsoft Network +Comment[nn]=Microsoft Network-protokoll +Comment[pl]=Protokół Microsoft Network +Comment[pt]=Protocolo da Microsoft Network +Comment[pt_BR]=Protocolo de Rede Microsoft +Comment[ru]=Протокол сети Microsoft Network +Comment[sl]=Protokol za povezavo na MSN +Comment[sv]=Microsoft-nätverksprotokoll +Comment[ta]=மைக்ரோசாப்ட் இணைய விதிமுறை +Comment[tg]=Қарордоди Шабакаи Microsoft +Comment[tr]=Microsoft Ağ Protokolü +Comment[uk]=Мережний протокол Microsoft +Comment[uz]=Microsoft tarmogʻi bilan aloqa oʻrnatish uchun protokol +Comment[uz@cyrillic]=Microsoft тармоғи билан алоқа ўрнатиш учун протокол +Comment[wa]=Protocole pol rantoele da Microsoft +Comment[zh_CN]=Microsoft Network 协议 +Comment[zh_HK]=Microsoft 網絡通訊協定 +Comment[zh_TW]=Microsoft 網路協定 + diff --git a/kopete/protocols/msn/config/msnpreferences.cpp b/kopete/protocols/msn/config/msnpreferences.cpp new file mode 100644 index 00000000..b28c2ea3 --- /dev/null +++ b/kopete/protocols/msn/config/msnpreferences.cpp @@ -0,0 +1,33 @@ +/* + msnpreferences.cpp - MSN Preferences Widget + + Copyright (c) 2002-2003 by Olivier Goffart <ogoffart @ kde.org> + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include <kgenericfactory.h> +#include "kcautoconfigmodule.h" +#include "msnprefs.h" + +class MSNPreferences; + +typedef KGenericFactory<MSNPreferences> MSNProtocolConfigFactory; +K_EXPORT_COMPONENT_FACTORY( kcm_kopete_msn, MSNProtocolConfigFactory( "kcm_kopete_msn" ) ) + +class MSNPreferences : public KCAutoConfigModule +{ +public: + MSNPreferences( QWidget *parent = 0, const char * = 0, const QStringList &args = QStringList() ) : KCAutoConfigModule( MSNProtocolConfigFactory::instance(), parent, args ) + { + setMainWidget( new msnPrefsUI( this ) , "MSN"); + } +}; diff --git a/kopete/protocols/msn/config/msnprefs.ui b/kopete/protocols/msn/config/msnprefs.ui new file mode 100644 index 00000000..c9321a60 --- /dev/null +++ b/kopete/protocols/msn/config/msnprefs.ui @@ -0,0 +1,217 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>msnPrefsUI</class> +<author>Duncan Mac-Vicar P.</author> +<widget class="QWidget"> + <property name="name"> + <cstring>msnPrefsUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>522</width> + <height>347</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel3_2_2_2_3</cstring> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>General</string> + </property> + </widget> + <widget class="QFrame"> + <property name="name"> + <cstring>Frame3_3_3_2_3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>NotifyNewChat</cstring> + </property> + <property name="text"> + <string>&Automatically open a chat window when someone starts a conversation</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>AutoDownloadPicture</cstring> + </property> + <property name="text"> + <string>&Automatically download the display picture if possible</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>useCustomEmoticons</cstring> + </property> + <property name="text"> + <string>Download and show custom emoticons (experimental)</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1_3_3_3</cstring> + </property> + <property name="text"> + <string></string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel3_2_2_2_2</cstring> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>Away Messages</string> + </property> + </widget> + <widget class="QFrame"> + <property name="name"> + <cstring>Frame3_3_3_2_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>HLine</enum> + </property> + <property name="frameShadow"> + <enum>Sunken</enum> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>SendAwayMessages</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>7</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Send &away messages</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout18</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel3</cstring> + </property> + <property name="text"> + <string>Do not send more than one away message every</string> + </property> + </widget> + <widget class="KIntNumInput"> + <property name="name"> + <cstring>AwayMessageSeconds</cstring> + </property> + <property name="value"> + <number>90</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel4</cstring> + </property> + <property name="text"> + <string>seconds</string> + </property> + </widget> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>70</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<connections> + <connection> + <sender>SendAwayMessages</sender> + <signal>toggled(bool)</signal> + <receiver>AwayMessageSeconds</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<tabstops> + <tabstop>NotifyNewChat</tabstop> + <tabstop>AutoDownloadPicture</tabstop> + <tabstop>useCustomEmoticons</tabstop> + <tabstop>SendAwayMessages</tabstop> + <tabstop>AwayMessageSeconds</tabstop> +</tabstops> +<includes> + <include location="global" impldecl="in implementation">knuminput.h</include> +</includes> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>knuminput.h</includehint> + <includehint>knuminput.h</includehint> +</includehints> +</UI> diff --git a/kopete/protocols/msn/dispatcher.cpp b/kopete/protocols/msn/dispatcher.cpp new file mode 100644 index 00000000..8b8bbed6 --- /dev/null +++ b/kopete/protocols/msn/dispatcher.cpp @@ -0,0 +1,647 @@ +/* + dispatcher.cpp - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "dispatcher.h" +#include "incomingtransfer.h" +#include "outgoingtransfer.h" + +#if MSN_WEBCAM +#include "webcam.h" +#endif + +using P2P::Dispatcher; +using P2P::Message; +using P2P::TransferContext; +using P2P::IncomingTransfer; +using P2P::OutgoingTransfer; + +#include "msnswitchboardsocket.h" + +// Kde includes +#include <kdebug.h> +#include <kmdcodec.h> +#include <kstandarddirs.h> +#include <ktempfile.h> + +// Qt includes +#include <qdatastream.h> +#include <qfile.h> +#include <qregexp.h> +#include <qtextcodec.h> +#include <qtextstream.h> + +// Kopete includes +#include <kopetechatsession.h> // Just for getting the contact +#include <kopeteaccount.h> +#include <kopetetransfermanager.h> + +#include <stdlib.h> + +Dispatcher::Dispatcher(QObject *parent, const QString& contact, const QStringList &ip) + : QObject(parent) , m_contact(contact) , m_callbackChannel(0l) , m_ip(ip) +{} + +Dispatcher::~Dispatcher() +{ + kdDebug(14140) << k_funcinfo << endl; + + if(m_callbackChannel) + { + delete m_callbackChannel; + m_callbackChannel = 0l; + } +} + +void Dispatcher::detach(TransferContext* transfer) +{ + m_sessions.remove(transfer->m_sessionId); + transfer->deleteLater(); +} + +QString Dispatcher::localContact() +{ + return m_contact; +} + +void Dispatcher::requestDisplayIcon(const QString& from, const QString& msnObject) +{ + Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4; + TransferContext* current = + new IncomingTransfer(from, this, sessionId); + + current->m_branch = P2P::Uid::createUid(); + current->m_callId = P2P::Uid::createUid(); + current->setType(P2P::UserDisplayIcon); + current->m_object = msnObject; + // Add the transfer to the list. + m_sessions.insert(sessionId, current); + + kdDebug(14140) << k_funcinfo << "Requesting, " << msnObject << endl; + + QString context = QString::fromUtf8(KCodecs::base64Encode(msnObject.utf8())); + // NOTE remove the \0 character automatically + // appended to a QCString. + context.replace("=", QString::null); + QString content = + "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n" + "SessionID: " + QString::number(sessionId) + "\r\n" + "AppID: 1\r\n" + "Context: " + context + "\r\n" + "\r\n"; + // Send the sending client an invitation message. + current->sendMessage(INVITE, content); +} + +void Dispatcher::sendFile(const QString& path, Q_INT64 fileSize, const QString& to) +{ + // Create a new transfer context that will handle + // the file transfer. + Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4; + TransferContext *current = + new OutgoingTransfer(to, this, sessionId); + current->m_branch = P2P::Uid::createUid(); + current->m_callId = P2P::Uid::createUid(); + current->setType(P2P::File); + // Add the transfer to the list. + m_sessions.insert(sessionId, current); + + // Set the transfer context file. + current->m_file = new QFile(path); + // Create the file context data. + QString context; + + QByteArray header(638); + header.fill('\0'); + QDataStream writer(header, IO_WriteOnly); + writer.setByteOrder(QDataStream::LittleEndian); + + // Write the header length to the stream. + writer << (Q_INT32)638; + // Write client version to the stream. + writer << (Q_INT32)3; + // Write the file size to the stream. + writer << fileSize; + // Write the file transfer flag to the stream. + // TODO support file preview. For now disable file preview. + writer << (Q_INT32)1; + // Write the file name in utf-16 to the stream. + QTextStream ts(header, IO_WriteOnly); + ts.setEncoding(QTextStream::RawUnicode); + ts.device()->at(20); + ts << path.section('/', -1); + // NOTE Background Sharing base64 [540..569] + // TODO add support for background sharing. + // Write file exchange type to the stream. + // NOTE File - 0xFFFFFFFF + // NOTE Background Sharing - 0xFFFFFFFE + writer.device()->at(570); + writer << (Q_UINT32)0xFFFFFFFF; + + // Encode the file context header to base64 encoding. + context = QString::fromUtf8(KCodecs::base64Encode(header)); + + // Send an INVITE message to the recipient. + QString content = "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\n" + "SessionID: " + QString::number(sessionId) + "\r\n" + "AppID: 2\r\n" + "Context: " + context + "\r\n" + "\r\n"; + current->sendMessage(INVITE, content); +} + +void Dispatcher::sendImage(const QString& /*fileName*/, const QString& /*to*/) +{ +// TODO kdDebug(14140) << k_funcinfo << endl; +// QFile imageFile(fileName); +// if(!imageFile.open(IO_ReadOnly)) +// { +// kdDebug(14140) << k_funcinfo << "Error opening image file." +// << endl; +// return; +// } +// +// OutgoingTransfer *outbound = +// new OutgoingTransfer(to, this, 64); +// +// outbound->sendImage(imageFile.readAll()); +} + +#if MSN_WEBCAM +void Dispatcher::startWebcam(const QString &/*myHandle*/, const QString &msgHandle, bool wantToReceive) +{ + Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4; + Webcam::Who who= wantToReceive ? Webcam::wViewer : Webcam::wProducer; + TransferContext* current = + new Webcam(who, msgHandle, this, sessionId); + + current->m_branch = P2P::Uid::createUid(); + current->m_callId = P2P::Uid::createUid(); + current->setType(P2P::WebcamType); + // Add the transfer to the list. + m_sessions.insert(sessionId, current); + + // {4BD96FC0-AB17-4425-A14A-439185962DC8} <- i want to show you my webcam + // {1C9AA97E-9C05-4583-A3BD-908A196F1E92} <- i want to see your webcam + QString GUID= (who==Webcam::wProducer) ? "4BD96FC0-AB17-4425-A14A-439185962DC8" : "1C9AA97E-9C05-4583-A3BD-908A196F1E92" ; + + QString content="EUF-GUID: {"+GUID+"}\r\n" + "SessionID: "+ QString::number(sessionId)+"\r\n" + "AppID: 4\r\n" + "Context: ewBCADgAQgBFADcAMABEAEUALQBFADIAQwBBAC0ANAA0ADAAMAAtAEEARQAwADMALQA4ADgARgBGADgANQBCADkARgA0AEUAOAB9AA==\r\n\r\n"; + + // context is the base64 of the utf16 of {B8BE70DE-E2CA-4400-AE03-88FF85B9F4E8} + + current->sendMessage( INVITE , content ); +} +#endif + + + +void Dispatcher::slotReadMessage(const QString &from, const QByteArray& stream) +{ + P2P::Message receivedMessage = + m_messageFormatter.readMessage(stream); + + receivedMessage.source = from; + + if(receivedMessage.contentType == "application/x-msnmsgrp2p") + { + if((receivedMessage.header.dataSize == 0)/* && ((receivedMessage.header.flag & 0x02) == 0x02)*/) + { + TransferContext *current = 0l; + QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin(); + for(; it != m_sessions.end(); it++) + { + if(receivedMessage.header.ackSessionIdentifier == it.data()->m_identifier){ + current = it.data(); + break; + } + } + + if(current){ + // Inform the transfer object of the acknowledge. + current->m_ackSessionIdentifier = receivedMessage.header.identifier; + current->m_ackUniqueIdentifier = receivedMessage.header.ackSessionIdentifier; + current->acknowledged(); + } + else + { + kdDebug(14140) << k_funcinfo + << "no transfer context with identifier, " + << receivedMessage.header.ackSessionIdentifier + << endl; + } + return; + } + + if(m_messageBuffer.contains(receivedMessage.header.identifier)) + { + kdDebug(14140) << k_funcinfo + << QString("retrieving buffered messsage, %1").arg(receivedMessage.header.identifier) + << endl; + + // The message was split, try to reconstruct the message + // with this received piece. + Message bufferedMessage = m_messageBuffer[receivedMessage.header.identifier]; + // Remove the buffered message. + m_messageBuffer.remove(receivedMessage.header.identifier); + + bufferedMessage.body.resize(bufferedMessage.body.size() + receivedMessage.header.dataSize); + for(Q_UINT32 i=0; i < receivedMessage.header.dataSize; i++){ + // Add the remaining message data to the buffered message. + bufferedMessage.body[receivedMessage.header.dataOffset + i] = receivedMessage.body[i]; + } + bufferedMessage.header.dataSize += receivedMessage.header.dataSize; + bufferedMessage.header.dataOffset = 0; + + receivedMessage = bufferedMessage; + } + + // Dispatch the received message. + dispatch(receivedMessage); + } +} + +void Dispatcher::dispatch(const P2P::Message& message) + +{ + TransferContext *messageHandler = 0l; + + if(message.header.sessionId > 0) + { + if(m_sessions.contains(message.header.sessionId)){ + messageHandler = m_sessions[message.header.sessionId]; + } + } + else + { + QString body = + QCString(message.body.data(), message.header.dataSize); + QRegExp regex("SessionID: ([0-9]*)\r\n"); + if(regex.search(body) > 0) + { + Q_UINT32 sessionId = regex.cap(1).toUInt(); + if(m_sessions.contains(sessionId)){ + // Retrieve the message handler associated with the specified session Id. + messageHandler = m_sessions[sessionId]; + } + } + else + { + // Otherwise, try to retrieve the message handler + // based on the acknowlegded unique identifier. + if(m_sessions.contains(message.header.ackUniqueIdentifier)){ + messageHandler = + m_sessions[message.header.ackUniqueIdentifier]; + } + + if(!messageHandler) + { + // If the message handler still has not been found, + // try to retrieve the handler based on the call id. + regex = QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + QString callId = regex.cap(1); + + TransferContext *current = 0l; + QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin(); + for(; it != m_sessions.end(); it++) + { + current = it.data(); + if(current->m_callId == callId){ + messageHandler = current; + break; + } + } + } + } + } + + if(messageHandler){ + // Process the received message using the + // retrieved registered handler. + messageHandler->m_ackSessionIdentifier = message.header.identifier; + messageHandler->m_ackUniqueIdentifier = message.header.ackSessionIdentifier; + messageHandler->processMessage(message); + } + else + { + // There are no objects registered, with the retrieved session Id, + // to handle the received message; default to this dispatcher. + + if(message.header.totalDataSize > message.header.dataOffset + message.header.dataSize) + { + // The entire message has not been received; + // buffer the recevied portion of the original message. + kdDebug(14140) << k_funcinfo + << QString("Buffering messsage, %1").arg(message.header.identifier) + << endl; + m_messageBuffer.insert(message.header.identifier, message); + return; + } + + QString body = + QCString(message.body.data(), message.header.dataSize); + kdDebug(14140) << k_funcinfo << "received, " << body << endl; + + if(body.startsWith("INVITE")) + { + // Retrieve the branch, call id, and session id. + // These fields will be used later on in the p2p + // transaction. + QRegExp regex(";branch=\\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + QString branch = regex.cap(1); + regex = QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + QString callId = regex.cap(1); + regex = QRegExp("SessionID: ([0-9]*)\r\n"); + regex.search(body); + QString sessionId = regex.cap(1); + // Retrieve the contact that requested the session. + regex = QRegExp("From: <msnmsgr:([^>]*)>"); + regex.search(body); + QString from = regex.cap(1); + // Retrieve the application identifier which + // is used to determine what type of session + // is being requested. + regex = QRegExp("AppID: ([0-9]*)\r\n"); + regex.search(body); + Q_UINT32 applicationId = regex.cap(1).toUInt(); + + if(applicationId == 1 || applicationId == 11 || applicationId == 12 ) + { //the AppID is 12 since Messenger 7.5 + // A contact has requested a session to download + // a display icon (User Display Icon or CustomEmotion). + + regex = QRegExp("Context: ([0-9a-zA-Z+/=]*)"); + regex.search(body); + QCString msnobj; + + // Decode the msn object from base64 encoding. + KCodecs::base64Decode(regex.cap(1).utf8() , msnobj); + kdDebug(14140) << k_funcinfo << "Contact requested, " + << msnobj << endl; + + // Create a new transfer context that will handle + // the user display icon transfer. + TransferContext *current = + new OutgoingTransfer(from, this, sessionId.toUInt()); + current->m_branch = branch; + current->m_callId = callId; + current->setType(P2P::UserDisplayIcon); + // Add the transfer to the list. + m_sessions.insert(sessionId.toUInt(), current); + + // Determine the display icon being requested. + QString fileName = objectList.contains(msnobj) + ? objectList[msnobj] + : m_pictureUrl; + QFile *source = new QFile(fileName); + // Try to open the source file for reading. + // If an error occurs, send an internal + // error message to the recipient. + if(!source->open(IO_ReadOnly)) + { + current->error(); + return; + } + + current->m_file = source; + // Acknowledge the session request. + current->acknowledge(message); + + current->m_ackSessionIdentifier = message.header.identifier; + current->m_ackUniqueIdentifier = message.header.ackSessionIdentifier; + // Send a 200 OK message to the recipient. + QString content = QString("SessionID: %1\r\n\r\n").arg(sessionId); + current->sendMessage(OK, content); + } + else if(applicationId == 2) + { + // A contact has requested a session to + // send a file. + + kdDebug(14140) << k_funcinfo << "File transfer invitation." << endl; + + // Create a new transfer context that will handle + // the file transfer. + TransferContext *transfer = + new IncomingTransfer(from, this, sessionId.toUInt()); + transfer->m_branch = branch; + transfer->m_callId = callId; + transfer->setType(P2P::File); + // Add the transfer to the list. + m_sessions.insert(sessionId.toUInt(), transfer); + + regex = QRegExp("Context: ([0-9a-zA-Z+/=]*)"); + regex.search(body); + QByteArray context; + + // Decode the file context from base64 encoding. + KCodecs::base64Decode(regex.cap(1).utf8(), context); + QDataStream reader(context, IO_ReadOnly); + reader.setByteOrder(QDataStream::LittleEndian); + //Retrieve the file info from the context field. + // File Size [8..15] Int64 + reader.device()->at(8); + Q_INT64 fileSize; + reader >> fileSize; + // Flag [15..18] Int32 + // 0x00 File transfer with preview data. + // 0x01 File transfer without preview data. + // 0x02 Background sharing. + Q_INT32 flag; + reader >> flag; + kdDebug(14140) << flag << endl; + // FileName UTF16 (Unicode) [19..539] + QByteArray bytes(520); + reader.readRawBytes(bytes.data(), bytes.size()); + QTextStream ts(bytes, IO_ReadOnly); + ts.setEncoding(QTextStream::Unicode); + QString fileName; + fileName = ts.readLine().utf8(); + + emit incomingTransfer(from, fileName, fileSize); + + kdDebug(14140) << + QString("%1, %2 bytes.").arg(fileName, QString::number(fileSize)) + << endl + << endl; + + // Get the contact that is sending the file. + Kopete::Contact *contact = getContactByAccountId(from); + + if(contact) + { + // Acknowledge the file invitation message. + transfer->acknowledge(message); + + transfer->m_ackSessionIdentifier = message.header.identifier; + transfer->m_ackUniqueIdentifier = message.header.ackSessionIdentifier; + + QObject::connect(Kopete::TransferManager::transferManager(), SIGNAL(accepted(Kopete::Transfer*, const QString&)), transfer, SLOT(slotTransferAccepted(Kopete::Transfer*, const QString&))); + QObject::connect(Kopete::TransferManager::transferManager(), SIGNAL(refused(const Kopete::FileTransferInfo&)), transfer, SLOT(slotTransferRefused(const Kopete::FileTransferInfo&))); + + // Show the file transfer accept/decline dialog. + Kopete::TransferManager::transferManager()->askIncomingTransfer(contact, fileName, fileSize, QString::null, sessionId); + } + else + { + kdWarning(14140) << fileName << " from " << from + << " has failed; could not retrieve contact from contact list." + << endl; + transfer->m_ackSessionIdentifier = message.header.identifier; + transfer->m_ackUniqueIdentifier = message.header.ackSessionIdentifier; + transfer->sendMessage(ERROR); + } + } + else if(applicationId == 4) + { +#if MSN_WEBCAM + regex = QRegExp("EUF-GUID: \\{([0-9a-zA-Z\\-]*)\\}"); + regex.search(body); + QString GUID=regex.cap(1); + + kdDebug(14140) << k_funcinfo << "webcam " << GUID << endl; + + Webcam::Who who; + if(GUID=="4BD96FC0-AB17-4425-A14A-439185962DC8") + { //that mean "I want to send MY webcam" + who=Webcam::wViewer; + } + else if(GUID=="1C9AA97E-9C05-4583-A3BD-908A196F1E92") + { //that mean "I want YOU to send YOUR webcam" + who=Webcam::wProducer; + } + else + { //unknown GUID + //current->error(); + kdWarning(14140) << k_funcinfo << "Unknown GUID " << GUID << endl; + return; + } + + TransferContext *current = new P2P::Webcam(who, from, this, sessionId.toUInt()); + current->m_branch = branch; + current->m_callId = callId; + + // Add the transfer to the list. + m_sessions.insert(sessionId.toUInt(), current); + // Acknowledge the session request. + current->acknowledge(message); + QTimer::singleShot(0,current, SLOT(askIncommingInvitation()) ); +#endif + } + } + else if(message.header.sessionId == 64) + { + // A contact has sent an inkformat (handwriting) gif. + // NOTE The entire message body is UTF16 encoded. + QString body = ""; + for (Q_UINT32 i=0; i < message.header.totalDataSize; i++){ + if (message.body[i] != QChar('\0')){ + body += QChar(message.body[i]); + } + } + + QRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); + regex.search(body); + QString contentType = regex.cap(1); + + if(contentType == "image/gif") + { + IncomingTransfer transfer(message.source, this, message.header.sessionId); + transfer.acknowledge(message); + + regex = QRegExp("base64:([0-9a-zA-Z+/=]*)"); + regex.search(body); + QString base64 = regex.cap(1); + QByteArray image; +// Convert from base64 encoding to byte array. + KCodecs::base64Decode(base64.utf8(), image); +// Create a temporary file to store the image data. + KTempFile *ink = new KTempFile(locateLocal("tmp", "inkformatgif-" ), ".gif"); + ink->setAutoDelete(true); +// Save the image data to disk. + ink->file()->writeBlock(image); + ink->file()->close(); + displayIconReceived(ink, "inkformatgif"); + ink = 0l; + } + } + } +} + +void Dispatcher::messageAcknowledged(unsigned int correlationId, bool fullReceive) +{ + if(fullReceive) + { + TransferContext *current = 0l; + QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin(); + for(; it != m_sessions.end(); it++) + { + current = it.data(); + if(current->m_transactionId == correlationId) + { + // Inform the transfer object of the acknowledge. + current->readyWrite(); + break; + } + } + } +} + +Kopete::Contact* Dispatcher::getContactByAccountId(const QString& accountId) +{ + Kopete::Contact *contact = 0l; + if(parent()) + { + // Retrieve the contact from the current chat session context. + Kopete::ChatSession *session = dynamic_cast<Kopete::ChatSession*>(parent()->parent()); + if(session) + { + contact = session->account()->contacts()[accountId]; + session->setCanBeDeleted(false); + } + } + return contact; +} + +Dispatcher::CallbackChannel::CallbackChannel(MSNSwitchBoardSocket *switchboard) +{ + m_switchboard = switchboard; +} + +Dispatcher::CallbackChannel::~CallbackChannel() +{} + +Q_UINT32 Dispatcher::CallbackChannel::send(const QByteArray& stream) +{ + return m_switchboard->sendCommand("MSG", "D", true, stream, true); +} + +Dispatcher::CallbackChannel* Dispatcher::callbackChannel() +{ + if(m_callbackChannel == 0l){ + MSNSwitchBoardSocket *callback = dynamic_cast<MSNSwitchBoardSocket *>(parent()); + if(callback == 0l) return 0l; + m_callbackChannel = new Dispatcher::CallbackChannel(callback); + } + + return m_callbackChannel; +} + +#include "dispatcher.moc" diff --git a/kopete/protocols/msn/dispatcher.h b/kopete/protocols/msn/dispatcher.h new file mode 100644 index 00000000..56bd1856 --- /dev/null +++ b/kopete/protocols/msn/dispatcher.h @@ -0,0 +1,107 @@ +/* + dispatcher.h - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef DISPATCHER_H +#define DISPATCHER_H + +#include <qobject.h> +#include <qstringlist.h> + +#include "kopete_export.h" + +#include "p2p.h" +#include "messageformatter.h" +#include "incomingtransfer.h" +#include "outgoingtransfer.h" + + +namespace Kopete { class Contact; } +class MSNSwitchBoardSocket; + +/** +@author Kopete Developers +*/ +namespace P2P{ + class IncomingTransfer; + class OutgoingTransfer; + + class KOPETE_EXPORT Dispatcher : public QObject + { Q_OBJECT + public: + Dispatcher(QObject *parent, const QString& contact, const QStringList &ip); + ~Dispatcher(); + + void detach(TransferContext* transfer); + QString localContact(); + void requestDisplayIcon(const QString& from, const QString& msnObject); + void sendFile(const QString& path, Q_INT64 fileSize, const QString& to); + void sendImage(const QString& fileName, const QString& to); + QString m_pictureUrl; + QMap<QString, QString> objectList; + +#if MSN_WEBCAM + void startWebcam(const QString &myHandle, const QString &msgHandle, bool wantToReceive); +#endif + + + public slots: + void slotReadMessage(const QString &from, const QByteArray& stream); + void messageAcknowledged(unsigned int correlationId, bool fullReceive); + + signals: + void sendCommand(const QString &cmd, const QString &args = QString::null, bool addId = true, const QByteArray &body = QByteArray(), bool binary=false); + void displayIconReceived(KTempFile* file, const QString& msnObject); + void incomingTransfer(const QString& from, const QString& fileName, Q_INT64 fileSize); + + private: + class CallbackChannel + { + public: + CallbackChannel(MSNSwitchBoardSocket *switchboard); + ~CallbackChannel(); + + Q_UINT32 send(const QByteArray& stream); + + private: + MSNSwitchBoardSocket *m_switchboard; + }; + + public: + CallbackChannel* callbackChannel(); + /** + * IP's of this compiter, the first one is the one seen by the server. + */ + QStringList localIp() { return m_ip; } + + + private: + void dispatch(const P2P::Message& message); + Kopete::Contact* getContactByAccountId(const QString& accountId); + + P2P::MessageFormatter m_messageFormatter; + QMap<Q_UINT32, P2P::TransferContext*> m_sessions; + QMap<Q_UINT32, P2P::Message> m_messageBuffer; + QString m_contact; + CallbackChannel *m_callbackChannel; + QStringList m_ip; + + friend class P2P::TransferContext; + friend class P2P::IncomingTransfer; + friend class P2P::OutgoingTransfer; + }; +} + +#endif diff --git a/kopete/protocols/msn/icons/Makefile.am b/kopete/protocols/msn/icons/Makefile.am new file mode 100644 index 00000000..9143c6b4 --- /dev/null +++ b/kopete/protocols/msn/icons/Makefile.am @@ -0,0 +1,2 @@ +kopeteicondir = $(kde_datadir)/kopete/icons +kopeteicon_ICON = AUTO diff --git a/kopete/protocols/msn/icons/cr128-app-msn_protocol.png b/kopete/protocols/msn/icons/cr128-app-msn_protocol.png Binary files differnew file mode 100644 index 00000000..dc94a4e9 --- /dev/null +++ b/kopete/protocols/msn/icons/cr128-app-msn_protocol.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_away.png b/kopete/protocols/msn/icons/cr16-action-msn_away.png Binary files differnew file mode 100644 index 00000000..cbbd45fc --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_away.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_blocked.png b/kopete/protocols/msn/icons/cr16-action-msn_blocked.png Binary files differnew file mode 100644 index 00000000..80efc4c7 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_blocked.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_brb.png b/kopete/protocols/msn/icons/cr16-action-msn_brb.png Binary files differnew file mode 100644 index 00000000..3f1a0d30 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_brb.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_busy.png b/kopete/protocols/msn/icons/cr16-action-msn_busy.png Binary files differnew file mode 100644 index 00000000..b3dcac08 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_busy.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng b/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng Binary files differnew file mode 100644 index 00000000..38629273 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng diff --git a/kopete/protocols/msn/icons/cr16-action-msn_invisible.png b/kopete/protocols/msn/icons/cr16-action-msn_invisible.png Binary files differnew file mode 100644 index 00000000..ce42bef0 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_invisible.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_lunch.png b/kopete/protocols/msn/icons/cr16-action-msn_lunch.png Binary files differnew file mode 100644 index 00000000..abf42e3f --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_lunch.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_na.png b/kopete/protocols/msn/icons/cr16-action-msn_na.png Binary files differnew file mode 100644 index 00000000..b1aa91af --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_na.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png b/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png Binary files differnew file mode 100644 index 00000000..d42bb0ae --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_offline.png b/kopete/protocols/msn/icons/cr16-action-msn_offline.png Binary files differnew file mode 100644 index 00000000..5cf9ffd5 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_offline.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_online.png b/kopete/protocols/msn/icons/cr16-action-msn_online.png Binary files differnew file mode 100644 index 00000000..71169ad2 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_online.png diff --git a/kopete/protocols/msn/icons/cr16-action-msn_phone.png b/kopete/protocols/msn/icons/cr16-action-msn_phone.png Binary files differnew file mode 100644 index 00000000..857ec14a --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-action-msn_phone.png diff --git a/kopete/protocols/msn/icons/cr16-app-msn_protocol.png b/kopete/protocols/msn/icons/cr16-app-msn_protocol.png Binary files differnew file mode 100644 index 00000000..a18ff5d4 --- /dev/null +++ b/kopete/protocols/msn/icons/cr16-app-msn_protocol.png diff --git a/kopete/protocols/msn/icons/cr32-app-msn_protocol.png b/kopete/protocols/msn/icons/cr32-app-msn_protocol.png Binary files differnew file mode 100644 index 00000000..2c9b130b --- /dev/null +++ b/kopete/protocols/msn/icons/cr32-app-msn_protocol.png diff --git a/kopete/protocols/msn/icons/cr48-app-msn_protocol.png b/kopete/protocols/msn/icons/cr48-app-msn_protocol.png Binary files differnew file mode 100644 index 00000000..ad495100 --- /dev/null +++ b/kopete/protocols/msn/icons/cr48-app-msn_protocol.png diff --git a/kopete/protocols/msn/icons/cr64-app-msn_protocol.png b/kopete/protocols/msn/icons/cr64-app-msn_protocol.png Binary files differnew file mode 100644 index 00000000..338f81bf --- /dev/null +++ b/kopete/protocols/msn/icons/cr64-app-msn_protocol.png diff --git a/kopete/protocols/msn/incomingtransfer.cpp b/kopete/protocols/msn/incomingtransfer.cpp new file mode 100644 index 00000000..99422ef7 --- /dev/null +++ b/kopete/protocols/msn/incomingtransfer.cpp @@ -0,0 +1,381 @@ +/* + incomingtransfer.cpp - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "incomingtransfer.h" +using P2P::TransferContext; +using P2P::IncomingTransfer; +using P2P::Message; + +// Kde includes +#include <kbufferedsocket.h> +#include <kdebug.h> +#include <klocale.h> +#include <kserversocket.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +using namespace KNetwork; + +// Qt includes +#include <qfile.h> +#include <qregexp.h> + +// Kopete includes +#include <kopetetransfermanager.h> + +IncomingTransfer::IncomingTransfer(const QString& from, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId) +: TransferContext(from,dispatcher,sessionId) +{ + m_direction = P2P::Incoming; + m_listener = 0l; +} + +IncomingTransfer::~IncomingTransfer() +{ + kdDebug(14140) << k_funcinfo << endl; + if(m_listener) + { + delete m_listener; + m_listener = 0l; + } + + if(m_socket) + { + delete m_socket; + m_socket = 0l; + } +} + + +void IncomingTransfer::slotTransferAccepted(Kopete::Transfer* transfer, const QString& /*fileName*/) +{ + Q_UINT32 sessionId = transfer->info().internalId().toUInt(); + if(sessionId!=m_sessionId) + return; + + QObject::connect(transfer , SIGNAL(transferCanceled()), this, SLOT(abort())); + m_transfer = transfer; + + QString content = QString("SessionID: %1\r\n\r\n").arg(sessionId); + sendMessage(OK, content); + + QObject::disconnect(Kopete::TransferManager::transferManager(), 0l, this, 0l); +} + +void IncomingTransfer::slotTransferRefused(const Kopete::FileTransferInfo& info) +{ + Q_UINT32 sessionId = info.internalId().toUInt(); + if(sessionId!=m_sessionId) + return; + + QString content = QString("SessionID: %1\r\n\r\n").arg(sessionId); + // Send the sending client a cancelation message. + sendMessage(DECLINE, content); + m_state=Finished; + + QObject::disconnect(Kopete::TransferManager::transferManager(), 0l, this, 0l); +} + + + +void IncomingTransfer::acknowledged() +{ + kdDebug(14140) << k_funcinfo << endl; + + switch(m_state) + { + case Invitation: + // NOTE UDI: base identifier acknowledge message, ignore. + // UDI: 200 OK message should follow. + if(m_type == File) + { + // FT: 200 OK acknowledged message. + // If this is the first connection between the two clients, a direct connection invitation + // should follow. Otherwise, the file transfer may start right away. + if(m_transfer) + { + QFile *destination = new QFile(m_transfer->destinationURL().path()); + if(!destination->open(IO_WriteOnly)) + { + m_transfer->slotError(KIO::ERR_CANNOT_OPEN_FOR_WRITING, i18n("Cannot open file for writing")); + m_transfer = 0l; + + error(); + return; + } + m_file = destination; + } + m_state = Negotiation; + } + break; + + case Negotiation: + // 200 OK acknowledge message. + break; + + case DataTransfer: + break; + + case Finished: + // UDI: Bye acknowledge message. + m_dispatcher->detach(this); + break; + } +} + +void IncomingTransfer::processMessage(const Message& message) +{ + if(m_file && (message.header.flag == 0x20 || message.header.flag == 0x01000030)) + { + // UserDisplayIcon data or File data is in this message. + // Write the recieved data to the file. + kdDebug(14140) << k_funcinfo << QString("Received, %1 bytes").arg(message.header.dataSize) << endl; + + m_file->writeBlock(message.body.data(), message.header.dataSize); + if(m_transfer){ + m_transfer->slotProcessed(message.header.dataOffset + message.header.dataSize); + } + + if((message.header.dataOffset + message.header.dataSize) == message.header.totalDataSize) + { + // Transfer is complete. + if(m_type == UserDisplayIcon){ + m_tempFile->close(); + m_dispatcher->displayIconReceived(m_tempFile, m_object); + m_tempFile = 0l; + m_file = 0l; + } + else + { + m_file->close(); + } + + m_isComplete = true; + // Send data acknowledge message. + acknowledge(message); + + if(m_type == UserDisplayIcon) + { + m_state = Finished; + // Send BYE message. + sendMessage(BYE, "\r\n"); + } + } + } + else if(message.header.dataSize == 4 && message.applicationIdentifier == 1) + { + // Data preparation message. + m_tempFile = new KTempFile(locateLocal("tmp", "msnpicture--"), ".png"); + m_tempFile->setAutoDelete(true); + m_file = m_tempFile->file(); + m_state = DataTransfer; + // Send data preparation acknowledge message. + acknowledge(message); + } + else + { + QString body = + QCString(message.body.data(), message.header.dataSize); +// kdDebug(14140) << k_funcinfo << "received, " << body << endl; + + if(body.startsWith("INVITE")) + { + // Retrieve some MSNSLP headers used when + // replying to this INVITE message. + QRegExp regex(";branch=\\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + m_branch = regex.cap(1); + // NOTE Call-ID never changes. + regex = QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + m_callId = regex.cap(1); + regex = QRegExp("Bridges: ([^\r\n]*)\r\n"); + regex.search(body); + QString bridges = regex.cap(1); + // The NetID field is 0 if the Conn-Type is + // Direct-Connect or Firewall, otherwise, it is + // a randomly generated number. + regex = QRegExp("NetID: (\\-?\\d+)\r\n"); + regex.search(body); + QString netId = regex.cap(1); + kdDebug(14140) << "net id, " << netId << endl; + // Connection Types + // - Direct-Connect + // - Port-Restrict-NAT + // - IP-Restrict-NAT + // - Symmetric-NAT + // - Firewall + regex = QRegExp("Conn-Type: ([^\r\n]+)\r\n"); + regex.search(body); + QString connType = regex.cap(1); + + bool wouldListen = false; + if(netId.toUInt() == 0 && connType == "Direct-Connect"){ + wouldListen = true; + + } + else if(connType == "IP-Restrict-NAT"){ + wouldListen = true; + } +#if 1 + wouldListen = false; // TODO Direct connection support +#endif + QString content; + + if(wouldListen) + { + // Create a listening socket for direct file transfer. + m_listener = new KServerSocket("", ""); + m_listener->setResolutionEnabled(true); + // Create the callback that will try to accept incoming connections. + QObject::connect(m_listener, SIGNAL(readyAccept()), SLOT(slotAccept())); + QObject::connect(m_listener, SIGNAL(gotError(int)), this, SLOT(slotListenError(int))); + // Listen for incoming connections. + bool isListening = m_listener->listen(1); + kdDebug(14140) << k_funcinfo << (isListening ? "listening" : "not listening") << endl; + kdDebug(14140) << k_funcinfo + << "local endpoint, " << m_listener->localAddress().nodeName() + << endl; + + content = "Bridge: TCPv1\r\n" + "Listening: true\r\n" + + QString("Hashed-Nonce: {%1}\r\n").arg(P2P::Uid::createUid()) + + QString("IPv4Internal-Addrs: %1\r\n").arg(m_listener->localAddress().nodeName()) + + QString("IPv4Internal-Port: %1\r\n").arg(m_listener->localAddress().serviceName()) + + "\r\n"; + } + else + { + content = + "Bridge: TCPv1\r\n" + "Listening: false\r\n" + "Hashed-Nonce: {00000000-0000-0000-0000-000000000000}\r\n" + "\r\n"; + } + + m_state = DataTransfer; + + if (m_type != File) + { + // NOTE For file transfers, the connection invite *must not* be acknowledged in any way + // as this trips MSN 7.5 + + acknowledge(message); + // Send 200 OK message to the sending client. + sendMessage(OK, content); + } + } + else if(body.startsWith("BYE")) + { + m_state = Finished; + // Send the sending client an acknowledge message. + acknowledge(message); + + if(m_file && m_transfer) + { + if(m_isComplete){ + // The transfer is complete. + m_transfer->slotComplete(); + } + else + { + // The transfer has been canceled remotely. + if(m_transfer){ + // Inform the user of the file transfer cancelation. + m_transfer->slotError(KIO::ERR_ABORTED, i18n("File transfer canceled.")); + } + // Remove the partially received file. + m_file->remove(); + } + } + + // Dispose of this transfer context. + m_dispatcher->detach(this); + } + else if(body.startsWith("MSNSLP/1.0 200 OK")) + { + if(m_type == UserDisplayIcon){ + m_state = Negotiation; + // Acknowledge the 200 OK message. + acknowledge(message); + } + } + } +} + +void IncomingTransfer::slotListenError(int /*errorCode*/) +{ + kdDebug(14140) << k_funcinfo << m_listener->errorString() << endl; +} + +void IncomingTransfer::slotAccept() +{ + // Try to accept an incoming connection from the sending client. + m_socket = static_cast<KBufferedSocket*>(m_listener->accept()); + if(!m_socket) + { + // NOTE If direct connection fails, the sending + // client wil transfer the file data through the + // existing session. + kdDebug(14140) << k_funcinfo << "Direct connection failed." << endl; + // Close the listening endpoint. + m_listener->close(); + return; + } + + kdDebug(14140) << k_funcinfo << "Direct connection established." << endl; + + // Set the socket to non blocking, + // enable the ready read signal and disable + // ready write signal. + // NOTE readyWrite consumes too much cpu usage. + m_socket->setBlocking(false); + m_socket->enableRead(true); + m_socket->enableWrite(false); + + // Create the callback that will try to read bytes from the accepted socket. + QObject::connect(m_socket, SIGNAL(readyRead()), this, SLOT(slotSocketRead())); + // Create the callback that will try to handle the socket close event. + QObject::connect(m_socket, SIGNAL(closed()), this, SLOT(slotSocketClosed())); + // Create the callback that will try to handle the socket error event. + QObject::connect(m_socket, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); +} + +void IncomingTransfer::slotSocketRead() +{ + int available = m_socket->bytesAvailable(); + kdDebug(14140) << k_funcinfo << available << ", bytes available." << endl; + if(available > 0) + { + QByteArray buffer(available); + m_socket->readBlock(buffer.data(), buffer.size()); + + if(QString(buffer) == "foo"){ + kdDebug(14140) << "Connection Check." << endl; + } + } +} + +void IncomingTransfer::slotSocketClosed() +{ + kdDebug(14140) << k_funcinfo << endl; +} + +void IncomingTransfer::slotSocketError(int errorCode) +{ + kdDebug(14140) << k_funcinfo << errorCode << endl; +} + +#include "incomingtransfer.moc" diff --git a/kopete/protocols/msn/incomingtransfer.h b/kopete/protocols/msn/incomingtransfer.h new file mode 100644 index 00000000..23e101b3 --- /dev/null +++ b/kopete/protocols/msn/incomingtransfer.h @@ -0,0 +1,57 @@ +/* + incomingtransfer.h - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef INCOMINGTRANSFER_H +#define INCOMINGTRANSFER_H + +#include "p2p.h" +#include "dispatcher.h" + +namespace KNetwork{ + class KServerSocket; +} + +/** +@author Kopete Developers +*/ +namespace P2P{ + class IncomingTransfer : public P2P::TransferContext + { Q_OBJECT + public: + IncomingTransfer(const QString& from, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId); + virtual ~IncomingTransfer(); + + private slots: + void slotListenError(int errorCode); + void slotAccept(); + void slotSocketRead(); + void slotSocketClosed(); + void slotSocketError(int errorCode); + + void slotTransferAccepted(Kopete::Transfer* transfer, const QString& fileName); + void slotTransferRefused(const Kopete::FileTransferInfo& info); + + + private: + virtual void acknowledged(); + virtual void processMessage(const Message& message); + + KTempFile *m_tempFile; + KNetwork::KServerSocket *m_listener; + }; +} + +#endif diff --git a/kopete/protocols/msn/kopete_msn.desktop b/kopete/protocols/msn/kopete_msn.desktop new file mode 100644 index 00000000..a8350f6b --- /dev/null +++ b/kopete/protocols/msn/kopete_msn.desktop @@ -0,0 +1,99 @@ +[Desktop Entry] +Type=Service +Icon=msn_protocol +ServiceTypes=Kopete/Protocol +X-KDE-Library=kopete_msn +X-Kopete-Version=1000900 +X-Kopete-Messaging-Protocol=messaging/msn +X-KDE-PluginInfo-Author=Kopete Developers +X-KDE-PluginInfo-Email=kopete-devel@kde.org +X-KDE-PluginInfo-Name=kopete_msn +X-KDE-PluginInfo-Version=0.8.0 +X-KDE-PluginInfo-Website=http://kopete.kde.org +X-KDE-PluginInfo-Category=Protocols +X-KDE-PluginInfo-Depends= +X-KDE-PluginInfo-License=GPL +X-KDE-PluginInfo-EnabledByDefault=false +Name=MSN Messenger +Name[ar]=مرسال MSN +Name[bn]=এমএসএন বার্তাবাহক +Name[cy]=Negesydd MSN +Name[da]=MSN-Messenger +Name[de]=MSN-Messenger +Name[eo]=MSN-mesaĝilo +Name[fa]=پیامرسان اماسان +Name[gl]=MSN Messanger +Name[hi]=एमएसएन मैसेंजर +Name[ja]=MSN メッセンジャー +Name[km]=កម្មវិធីផ្ញើសារ MSN +Name[lt]=MSN žinučių klientas +Name[mk]=Гласник за MSN +Name[nds]=MSN-Kortnarichtendeenst +Name[ne]=एमएसएन मेसेन्जर +Name[pa]=MSN ਸੁਨੇਹੇਦਾਰ +Name[pl]=Komunikator MSN Messenger +Name[pt_BR]=Mensageiro MSN +Name[ro]=Mesaje instantanee MSN +Name[tg]=MSN Пайёмбар +Name[uk]=Кур'єр MSN +Name[uz]=MSN mesenjer +Name[uz@cyrillic]=MSN месенжер +Comment=Protocol to connect to MSN Messenger +Comment[ar]=البرتوكول سيتصل بمرسال MSN +Comment[be]=Пратакол MSN Messenger +Comment[bg]=Протокол за връзка с MSN Messenger +Comment[bn]=এমএসএন বার্তাবাহকে সংযোগ করতে প্রোটোকল +Comment[br]=Komenad kevreañ ouzh MSN Messenger +Comment[bs]=MSN Messenger protokol +Comment[ca]=Protocol per a connectar-se a MSN Messenger +Comment[cs]=Protokol k připojení k MSN Messengeru +Comment[cy]=Protocol i gysylltu â Negesydd MSN +Comment[da]=Protokol til at forbinde til MSN-Messenger +Comment[de]=Protokoll zur Verbindung mit dem MSN-Messenger +Comment[el]=Πρωτόκολλο για σύνδεση στο MSN Messenger +Comment[es]=Protocolo para conectar con MSN Messenger +Comment[et]=Protokoll ühendumiseks MSN Messengeriga +Comment[eu]=MSN Messenger-era konektatzeko protokoloa +Comment[fa]=قرارداد برای اتصال به پیامرسان اماسان +Comment[fi]=Yhteyskäytäntö MSN Messanger -verkkoon kytkeytymiseen +Comment[fr]=Protocole pour se connecter sur MSN Messenger +Comment[ga]=Prótacal chun ceangal le MSN Messenger +Comment[gl]=Protocolo para se conectar ó MSN Messanger +Comment[he]=פרוטוקול התחברות ל- MSN Messenger +Comment[hi]=एमएसएन मैसेंजर से जुड़ने का प्रोटोकॉल +Comment[hr]=Protokol za povezivanje na MSN Messenger +Comment[hu]=Protokoll az MSN Messenger használatához +Comment[is]=Samskiptamáti til að tengjast MSN Messenger +Comment[it]=Protocollo per connessione a MSN Messenger +Comment[ja]=MSN メッセンジャーに接続するプロトコル +Comment[ka]=MSN Messenger დაკავშირების ოქმი +Comment[kk]=MSN Messenger-ге қосылу протоколы +Comment[km]=ពិធីការដើម្បីភ្ជាប់ទៅកម្មវិធីផ្ញើសារ MSN +Comment[lt]=Protokolas prisijungimui prie MSN žinučių kliento +Comment[mk]=Протокол за поврзување на Гласникот на MSN +Comment[nb]=Protokoll for å koble til MSN Messenger +Comment[nds]=Protokoll för't Tokoppeln na den MSN-Kortnarichtendeenst +Comment[ne]=एमएसएन मेसेन्जरमा जडान गर्नुपर्ने प्रोटोकल +Comment[nl]=Protocol voor MSN Messenger +Comment[nn]=Protokoll for å kopla til MSN Messenger +Comment[pl]=Protokół połączenia z serwerem MSN Messenger +Comment[pt]=Um protocolo para ser ligar ao MSN Messenger +Comment[pt_BR]=Protocolo para conexão ao MSN Messenger +Comment[ro]=Protocol de conectare la MSN Messenger +Comment[ru]=Протокол для подключения к MSN Messenger +Comment[sk]=Protokol pre pripojenie k MSN Messenger +Comment[sl]=Protokol za povezavo na MSN Messenger +Comment[sr]=Протокол за повезивање на MSN Messenger +Comment[sr@Latn]=Protokol za povezivanje na MSN Messenger +Comment[sv]=Protokoll för att ansluta till MSN-meddelandeklient +Comment[ta]= MSN Messenger யுடன் இணைக்க விதிமுறை +Comment[tg]=Қарордоди пайвастшавӣ ба MSN Пайёмбар +Comment[tr]=MSN Messenger'a bağlantı iletişim kuralı +Comment[uk]=Протокол для з'єднання з MSN Messenger +Comment[uz]=MSN mesenjer bilan aloqa oʻrnatish uchun protokol +Comment[uz@cyrillic]=MSN месенжер билан алоқа ўрнатиш учун протокол +Comment[wa]=Protocole po s' raloyî a MSN +Comment[zh_CN]=连接到 MSN Messenger 协议 +Comment[zh_HK]=用來連接至 MSN Messenger 的通訊協定 +Comment[zh_TW]=連線到 MSN 的協定 + diff --git a/kopete/protocols/msn/messageformatter.cpp b/kopete/protocols/msn/messageformatter.cpp new file mode 100644 index 00000000..3a698ac4 --- /dev/null +++ b/kopete/protocols/msn/messageformatter.cpp @@ -0,0 +1,192 @@ +/* + messageformatter.cpp - msn p2p protocol + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "messageformatter.h" +#include "p2p.h" + +// Qt includes +#include <qdatastream.h> +#include <qregexp.h> + +// Kde includes +#include <kdebug.h> + +using P2P::MessageFormatter; +using P2P::Message; + +MessageFormatter::MessageFormatter(QObject *parent, const char *name) : QObject(parent, name) +{} + +MessageFormatter::~MessageFormatter() +{} + +Message MessageFormatter::readMessage(const QByteArray& stream, bool compact) +{ + Message inbound; + + Q_UINT32 index = 0; + if(compact == false) + { + // Determine the end position of the message header. + while(index < stream.size()) + { + if(stream[index++] == '\n'){ + if(stream[index - 3] == '\n') + break; + } + } + + // Retrieve the message header. + QString messageHeader = QCString(stream.data(), index); + + // Retrieve the message mime version, content type, + // and p2p destination. + QRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); + regex.search(messageHeader); + QString contentType = regex.cap(1); + + if(contentType != "application/x-msnmsgrp2p") + return inbound; + +// kdDebug(14140) << k_funcinfo << endl; + + regex = QRegExp("MIME-Version: (\\d.\\d)"); + regex.search(messageHeader); + inbound.mimeVersion = regex.cap(1); + inbound.contentType = contentType; + regex = QRegExp("P2P-Dest: ([^\r\n]*)"); + regex.search(messageHeader); + QString destination = regex.cap(1); + } + + QDataStream reader(stream, IO_ReadOnly); + reader.setByteOrder(QDataStream::LittleEndian); + // Seek to the start position of the message + // transport header. + reader.device()->at(index); + + // Read the message transport headers from the stream. + reader >> inbound.header.sessionId; + reader >> inbound.header.identifier; + reader >> inbound.header.dataOffset; + reader >> inbound.header.totalDataSize; + reader >> inbound.header.dataSize; + reader >> inbound.header.flag; + reader >> inbound.header.ackSessionIdentifier; + reader >> inbound.header.ackUniqueIdentifier; + reader >> inbound.header.ackDataSize; + + /*kdDebug(14140) + << "session id, " << inbound.header.sessionId << endl + << "identifier, " << inbound.header.identifier << endl + << "data offset, " << inbound.header.dataOffset << endl + << "total size, " << inbound.header.totalDataSize << endl + << "data size, " << inbound.header.dataSize << endl + << "flag, " << inbound.header.flag << endl + << "ack session identifier, " << inbound.header.ackSessionIdentifier << endl + << "ack unique identifier, " << inbound.header.ackUniqueIdentifier << endl + << "ack data size, " << inbound.header.ackDataSize + << endl;*/ + + // Read the message body from the stream. + if(inbound.header.dataSize > 0){ + inbound.body.resize(inbound.header.dataSize); + reader.readRawBytes(inbound.body.data(), inbound.header.dataSize); + } + + if(compact == false) + { + reader.setByteOrder(QDataStream::BigEndian); + // Read the message application identifier from the stream. + reader >> inbound.applicationIdentifier; + +/* kdDebug(14140) + << "application identifier, " << inbound.applicationIdentifier + << endl;*/ + } + + return inbound; +} + +void MessageFormatter::writeMessage(const Message& message, QByteArray& stream, bool compact) +{ +// kdDebug(14140) << k_funcinfo << endl; + + QDataStream writer(stream, IO_WriteOnly); + writer.setByteOrder(QDataStream::LittleEndian); + + if(compact == false) + { + const QCString messageHeader = QString("MIME-Version: 1.0\r\n" + "Content-Type: application/x-msnmsgrp2p\r\n" + "P2P-Dest: " + message.destination + "\r\n" + "\r\n").utf8(); + // Set the capacity of the message buffer. + stream.resize(messageHeader.length() + 48 + message.body.size() + 4); + // Write the message header to the stream + writer.writeRawBytes(messageHeader.data(), messageHeader.length()); + } + else + { + // Set the capacity of the message buffer. + stream.resize(4 + 48 + message.body.size()); + // Write the message size to the stream. + writer << (Q_INT32)(48+message.body.size()); + } + + + // Write the transport headers to the stream. + writer << message.header.sessionId; + writer << message.header.identifier; + writer << message.header.dataOffset; + writer << message.header.totalDataSize; + writer << message.header.dataSize; + writer << message.header.flag; + writer << message.header.ackSessionIdentifier; + writer << message.header.ackUniqueIdentifier; + writer << message.header.ackDataSize; + +/* kdDebug(14140) + << "session id, " << message.header.sessionId << endl + << "identifier, " << message.header.identifier << endl + << "data offset, " << message.header.dataOffset << endl + << "total size, " << message.header.totalDataSize << endl + << "data size, " << message.header.dataSize << endl + << "flag, " << message.header.flag << endl + << "ack session identifier, " << message.header.ackSessionIdentifier << endl + << "ack unique identifier, " << message.header.ackUniqueIdentifier << endl + << "ack data size, " << message.header.ackDataSize + << endl; +*/ + if(message.body.size() > 0){ + // Write the messge body to the stream. + writer.writeRawBytes(message.body.data(), message.body.size()); + } + + if(compact == false) + { + // Seek to the message application identifier section. + writer.setByteOrder(QDataStream::BigEndian); + // Write the message application identifier to the stream. + writer << message.applicationIdentifier; + +/* kdDebug(14140) + << "application identifier, " << message.applicationIdentifier + << endl; + */ + } +} + +#include "messageformatter.moc" diff --git a/kopete/protocols/msn/messageformatter.h b/kopete/protocols/msn/messageformatter.h new file mode 100644 index 00000000..9eae8682 --- /dev/null +++ b/kopete/protocols/msn/messageformatter.h @@ -0,0 +1,40 @@ +/* + messageformatter.h - msn p2p protocol + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MESSAGEFORMATTER_H +#define MESSAGEFORMATTER_H + +#include <qobject.h> + +namespace P2P{ + class Message; +} + +/** +@author Kopete Developers +*/ +namespace P2P{ + class MessageFormatter : public QObject + { Q_OBJECT + public: + MessageFormatter(QObject *parent = 0, const char *name = 0); + ~MessageFormatter(); + + Message readMessage(const QByteArray& stream, bool compact=false); + void writeMessage(const Message& message, QByteArray& stream, bool compact=false); + }; +} + +#endif diff --git a/kopete/protocols/msn/msnaccount.cpp b/kopete/protocols/msn/msnaccount.cpp new file mode 100644 index 00000000..01caec11 --- /dev/null +++ b/kopete/protocols/msn/msnaccount.cpp @@ -0,0 +1,1499 @@ +/* + msnaccount.h - Manages a single MSN account + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnaccount.h" + +#include <config.h> + +#include <kaction.h> +#include <kconfig.h> +#include <kdebug.h> +#include <kinputdialog.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <kstandarddirs.h> +#include <kmdcodec.h> +#include <klocale.h> + +#include <qfile.h> +#include <qregexp.h> +#include <qvalidator.h> +#include <qimage.h> + +#include "msncontact.h" +#include "msnnotifysocket.h" +#include "msnchatsession.h" +#include "kopetecontactlist.h" +#include "kopetegroup.h" +#include "kopetemetacontact.h" +#include "kopetepassword.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopetechatsessionmanager.h" +#include "contactaddednotifydialog.h" +#include "kopeteutils.h" + +#include "sha1.h" + + +#if !defined NDEBUG +#include "msndebugrawcmddlg.h" +#include <kglobal.h> +#endif + +#if MSN_WEBCAM +#include "avdevice/videodevicepool.h" +#endif + +MSNAccount::MSNAccount( MSNProtocol *parent, const QString& AccountID, const char *name ) + : Kopete::PasswordedAccount ( parent, AccountID.lower(), 0, name ) +{ + m_notifySocket = 0L; + m_connectstatus = MSNProtocol::protocol()->NLN; + m_addWizard_metaContact = 0L; + m_newContactList=false; + + // Init the myself contact + setMyself( new MSNContact( this, accountId(), Kopete::ContactList::self()->myself() ) ); + //myself()->setOnlineStatus( MSNProtocol::protocol()->FLN ); + + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRenamed( Kopete::Group *, const QString & ) ), + SLOT( slotKopeteGroupRenamed( Kopete::Group * ) ) ); + QObject::connect( Kopete::ContactList::self(), SIGNAL( groupRemoved( Kopete::Group * ) ), + SLOT( slotKopeteGroupRemoved( Kopete::Group * ) ) ); + + QObject::connect( Kopete::ContactList::self(), SIGNAL( globalIdentityChanged(const QString&, const QVariant& ) ), SLOT( slotGlobalIdentityChanged(const QString&, const QVariant& ) )); + + m_openInboxAction = new KAction( i18n( "Open Inbo&x..." ), "mail_generic", 0, this, SLOT( slotOpenInbox() ), this, "m_openInboxAction" ); + m_changeDNAction = new KAction( i18n( "&Change Display Name..." ), QString::null, 0, this, SLOT( slotChangePublicName() ), this, "renameAction" ); + m_startChatAction = new KAction( i18n( "&Start Chat..." ), "mail_generic", 0, this, SLOT( slotStartChat() ), this, "startChatAction" ); + + + KConfigGroup *config=configGroup(); + + m_blockList = config->readListEntry( "blockList" ) ; + m_allowList = config->readListEntry( "allowList" ) ; + m_reverseList = config->readListEntry( "reverseList" ) ; + + // Load the avatar + m_pictureFilename = locateLocal( "appdata", "msnpicture-"+ accountId().lower().replace(QRegExp("[./~]"),"-") +".png" ); + resetPictureObject(true); + + static_cast<MSNContact *>( myself() )->setInfo( "PHH", config->readEntry("PHH") ); + static_cast<MSNContact *>( myself() )->setInfo( "PHM", config->readEntry("PHM") ); + static_cast<MSNContact *>( myself() )->setInfo( "PHW", config->readEntry("PHW") ); + //this is the display name + static_cast<MSNContact *>( myself() )->setInfo( "MFN", config->readEntry("MFN") ); + + //construct the group list + //Before 2003-11-14 the MSN server allowed us to download the group list without downloading the whole contactlist, but it's not possible anymore + QPtrList<Kopete::Group> groupList = Kopete::ContactList::self()->groups(); + for ( Kopete::Group *g = groupList.first(); g; g = groupList.next() ) + { + QString groupGuid=g->pluginData( protocol(), accountId() + " id" ); + if ( !groupGuid.isEmpty() ) + m_groupList.insert( groupGuid , g ); + } + + // Set the client Id for the myself contact. It sets what MSN feature we support. + m_clientId = MSNProtocol::MSNC4 | MSNProtocol::InkFormatGIF | MSNProtocol::SupportMultiPacketMessaging; + +#if MSN_WEBCAM + Kopete::AV::VideoDevicePool::self()->scanDevices(); + if( Kopete::AV::VideoDevicePool::self()->hasDevices() ) + { + m_clientId |= MSNProtocol::SupportWebcam; + } +#endif +} + + +QString MSNAccount::serverName() +{ + return configGroup()->readEntry( "serverName" , "messenger.hotmail.com" ); +} + +uint MSNAccount::serverPort() +{ + return configGroup()->readNumEntry( "serverPort" , 1863 ); +} + +bool MSNAccount::useHttpMethod() const +{ + return configGroup()->readBoolEntry( "useHttpMethod" , false ); +} + +QString MSNAccount::myselfClientId() const +{ + return QString::number(m_clientId, 10); +} + +void MSNAccount::connectWithPassword( const QString &passwd ) +{ + m_newContactList=false; + if ( isConnected() ) + { + kdDebug( 14140 ) << k_funcinfo <<"Ignoring Connect request " + << "(Already Connected)" << endl; + return; + } + + if ( m_notifySocket ) + { + kdDebug( 14140 ) << k_funcinfo <<"Ignoring Connect request (Already connecting)" << endl; + return; + } + + m_password = passwd; + + if ( m_password.isNull() ) + { + kdDebug( 14140 ) << k_funcinfo <<"Abort connection (null password)" << endl; + return; + } + + + if ( contacts().count() <= 1 ) + { + // Maybe the contactlist.xml has been removed, and the serial number not updated + // ( the 1 is for the myself contact ) + configGroup()->writeEntry( "serial", 0 ); + } + + m_openInboxAction->setEnabled( false ); + + createNotificationServer(serverName(), serverPort()); +} + +void MSNAccount::createNotificationServer( const QString &host, uint port ) +{ + if(m_notifySocket) //we are switching from one to another notifysocket. + { + //remove every slots to that socket, so we won't delete receive signals + // from the old socket thinking they are from the new one + QObject::disconnect( m_notifySocket , 0, this, 0 ); + m_notifySocket->deleteLater(); //be sure it will be deleted + m_notifySocket=0L; + } + + m_msgHandle.clear(); + + myself()->setOnlineStatus( MSNProtocol::protocol()->CNT ); + + + m_notifySocket = new MSNNotifySocket( this, accountId() , m_password); + m_notifySocket->setUseHttpMethod( useHttpMethod() ); + + QObject::connect( m_notifySocket, SIGNAL( groupAdded( const QString&, const QString& ) ), + SLOT( slotGroupAdded( const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( groupRenamed( const QString&, const QString& ) ), + SLOT( slotGroupRenamed( const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( groupListed( const QString&, const QString& ) ), + SLOT( slotGroupAdded( const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( groupRemoved( const QString& ) ), + SLOT( slotGroupRemoved( const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( contactList(const QString&, const QString&, const QString&, uint, const QString& ) ), + SLOT( slotContactListed(const QString&, const QString&, const QString&, uint, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL(contactAdded(const QString&, const QString&, const QString&, const QString&, const QString& ) ), + SLOT( slotContactAdded(const QString&, const QString&, const QString&, const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( contactRemoved(const QString&, const QString&, const QString&, const QString& ) ), + SLOT( slotContactRemoved(const QString&, const QString&, const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( statusChanged( const Kopete::OnlineStatus & ) ), + SLOT( slotStatusChanged( const Kopete::OnlineStatus & ) ) ); + QObject::connect( m_notifySocket, SIGNAL( invitedToChat( const QString&, const QString&, const QString&, const QString&, const QString& ) ), + SLOT( slotCreateChat( const QString&, const QString&, const QString&, const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( startChat( const QString&, const QString& ) ), + SLOT( slotCreateChat( const QString&, const QString& ) ) ); + QObject::connect( m_notifySocket, SIGNAL( socketClosed() ), + SLOT( slotNotifySocketClosed() ) ); + QObject::connect( m_notifySocket, SIGNAL( newContactList() ), + SLOT( slotNewContactList() ) ); + QObject::connect( m_notifySocket, SIGNAL( receivedNotificationServer(const QString&, uint ) ), + SLOT(createNotificationServer(const QString&, uint ) ) ); + QObject::connect( m_notifySocket, SIGNAL( hotmailSeted( bool ) ), + m_openInboxAction, SLOT( setEnabled( bool ) ) ); + QObject::connect( m_notifySocket, SIGNAL( errorMessage(int, const QString& ) ), + SLOT( slotErrorMessageReceived(int, const QString& ) ) ); + + m_notifySocket->setStatus( m_connectstatus ); + m_notifySocket->connect(host, port); +} + +void MSNAccount::disconnect() +{ + if ( m_notifySocket ) + m_notifySocket->disconnect(); +} + +KActionMenu * MSNAccount::actionMenu() +{ + KActionMenu *m_actionMenu=Kopete::Account::actionMenu(); + if ( isConnected() ) + { + m_openInboxAction->setEnabled( true ); + m_startChatAction->setEnabled( true ); + m_changeDNAction->setEnabled( true ); + } + else + { + m_openInboxAction->setEnabled( false ); + m_startChatAction->setEnabled( false ); + m_changeDNAction->setEnabled( false ); + } + + m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert( m_changeDNAction ); + m_actionMenu->insert( m_startChatAction ); + +// m_actionMenu->popupMenu()->insertSeparator(); + + m_actionMenu->insert( m_openInboxAction ); + +#if !defined NDEBUG + KActionMenu *debugMenu = new KActionMenu( "Debug", m_actionMenu ); + debugMenu->insert( new KAction( i18n( "Send Raw C&ommand..." ), 0, + this, SLOT( slotDebugRawCommand() ), debugMenu, "m_debugRawCommand" ) ); + m_actionMenu->popupMenu()->insertSeparator(); + m_actionMenu->insert( debugMenu ); +#endif + + return m_actionMenu; +} + +MSNNotifySocket *MSNAccount::notifySocket() +{ + return m_notifySocket; +} + + +void MSNAccount::setOnlineStatus( const Kopete::OnlineStatus &status , const QString &reason) +{ + kdDebug( 14140 ) << k_funcinfo << status.description() << endl; + + // HACK: When changing song, do don't anything while connected + if( reason.contains("[Music]") && ( status == MSNProtocol::protocol()->UNK || status == MSNProtocol::protocol()->CNT ) ) + return; + + // Only send personal message when logged. + if( m_notifySocket && m_notifySocket->isLogged() ) + { + // Only update the personal/status message, don't change the online status + // since it's the same. + if( reason.contains("[Music]") ) + { + QString personalMessage = reason.section("[Music]", 1); + setPersonalMessage( MSNProtocol::PersonalMessageMusic, personalMessage ); + + // Don't send un-needed status change. + return; + } + else + { + setPersonalMessage( MSNProtocol::PersonalMessageNormal, reason ); + } + } + + if(status.status()== Kopete::OnlineStatus::Offline) + disconnect(); + else if ( m_notifySocket ) + { + m_notifySocket->setStatus( status ); + } + else + { + m_connectstatus = status; + connect(); + } + + +} + +void MSNAccount::slotStartChat() +{ + + bool ok; + QString handle = KInputDialog::getText( i18n( "Start Chat - MSN Plugin" ), + i18n( "Please enter the email address of the person with whom you want to chat:" ), QString::null, &ok ).lower(); + if ( ok ) + { + if ( MSNProtocol::validContactId( handle ) ) + { + if ( !contacts()[ handle ] ) + addContact( handle, handle, 0L, Kopete::Account::Temporary ); + + contacts()[ handle ]->execute(); + } + else + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>You must enter a valid email address.</qt>" ), i18n( "MSN Plugin" ) ); + } + } +} + +void MSNAccount::slotDebugRawCommand() +{ +#if !defined NDEBUG + if ( !isConnected() ) + return; + + MSNDebugRawCmdDlg *dlg = new MSNDebugRawCmdDlg( 0L ); + int result = dlg->exec(); + if ( result == QDialog::Accepted && m_notifySocket ) + { + m_notifySocket->sendCommand( dlg->command(), dlg->params(), + dlg->addId(), dlg->msg().replace( "\n", "\r\n" ).utf8() ); + } + delete dlg; +#endif +} + +void MSNAccount::slotChangePublicName() +{ + if ( !isConnected() ) + { + return; + //TODO: change it anyway, and sync at the next connection + } + + bool ok; + QString name = KInputDialog::getText( i18n( "Change Display Name - MSN Plugin" ), + i18n( "Enter the new display name by which you want to be visible to your friends on MSN:" ), + myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(), &ok ); + + if ( ok ) + { + if ( name.length() > 387 ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n( "<qt>The display name you entered is too long. Please use a shorter name.\n" + "Your display name has <b>not</b> been changed.</qt>" ), + i18n( "Change Display Name - MSN Plugin" ) ); + return; + } + + setPublicName( name ); + } +} + + +void MSNAccount::slotOpenInbox() +{ + if ( m_notifySocket ) + m_notifySocket->slotOpenInbox(); +} + + +void MSNAccount::slotNotifySocketClosed() +{ + kdDebug( 14140 ) << k_funcinfo << endl; + + Kopete::Account::DisconnectReason reason=(Kopete::Account::DisconnectReason)(m_notifySocket->disconnectReason()); + m_notifySocket->deleteLater(); + m_notifySocket = 0l; + myself()->setOnlineStatus( MSNProtocol::protocol()->FLN ); + setAllContactsStatus( MSNProtocol::protocol()->FLN ); + disconnected(reason); + + + if(reason == Kopete::Account::OtherClient) + { //close all chat sessions, so new message will arive to the other client. + + QValueList<Kopete::ChatSession*> sessions = Kopete::ChatSessionManager::self()->sessions(); + QValueList<Kopete::ChatSession*>::Iterator it; + for (it=sessions.begin() ; it != sessions.end() ; it++ ) + { + MSNChatSession *msnCS = dynamic_cast<MSNChatSession *>( *it ); + if ( msnCS && msnCS->account() == this ) + { + msnCS->slotCloseSession(); + } + } + } + +#if 0 + else if ( state == 0x10 ) // connection died unexpectedly + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error , i18n( "The connection with the MSN server was lost unexpectedly.\n" + "If you cannot reconnect now, the server might be down. In that case, please try again later." ), + i18n( "Connection Lost - MSN Plugin" ), KMessageBox::Notify ); + } +#endif + m_msgHandle.clear(); + // kdDebug( 14140 ) << "MSNAccount::slotNotifySocketClosed - done" << endl; +} + +void MSNAccount::slotStatusChanged( const Kopete::OnlineStatus &status ) +{ +// kdDebug( 14140 ) << k_funcinfo << status.internalStatus() << endl; + myself()->setOnlineStatus( status ); + + if(m_newContactList) + { + m_newContactList=false; + + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + MSNContact *c = static_cast<MSNContact *>( *it ); + if(c && c->isDeleted() && c->metaContact() && !c->metaContact()->isTemporary() && c!=myself()) + { + if(c->serverGroups().isEmpty()) + { //the contact is new, add it on the server + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + addContactServerside( c->contactId() , c->metaContact()->groups() ); + } + else //the contact had been deleted, remove it. + { + c->deleteLater(); + } + } + } + } +} + + +void MSNAccount::slotPersonalMessageChanged( const QString& personalMessage ) +{ + QString oldPersonalMessage=myself()->property(MSNProtocol::protocol()->propPersonalMessage).value().toString() ; + if ( personalMessage != oldPersonalMessage ) + { + myself()->setProperty( MSNProtocol::protocol()->propPersonalMessage, personalMessage ); + configGroup()->writeEntry( "personalMessage" , personalMessage ); + } +} + +void MSNAccount::setPublicName( const QString &publicName ) +{ + if ( m_notifySocket ) + { + m_notifySocket->changePublicName( publicName, QString::null ); + } +} + +void MSNAccount::setPersonalMessage( MSNProtocol::PersonalMessageType type, const QString &personalMessage ) +{ + if ( m_notifySocket ) + { + m_notifySocket->changePersonalMessage( type, personalMessage ); + } + /* Eh, if we can't change the display name, don't let make the user think it has changed + else if(type == MSNProtocol::PersonalMessageNormal) // Normal personalMessage, not a dynamic one that need formatting. + { + slotPersonalMessageChanged( personalMessage ); + }*/ +} + +void MSNAccount::slotGroupAdded( const QString& groupName, const QString &groupGuid ) +{ + if ( m_groupList.contains( groupGuid ) ) + { + // Group can already be in the list since the idle timer does a 'List Groups' + // command. Simply return, don't issue a warning. + // kdDebug( 14140 ) << k_funcinfo << "Group " << groupName << " already in list, skipped." << endl; + return; + } + + //--------- Find the appropriate Kopete::Group, or create one ---------// + QPtrList<Kopete::Group> groupList = Kopete::ContactList::self()->groups(); + Kopete::Group *fallBack = 0L; + + //check if we have one in the old group list. if yes, update the id translate map. + for(QMap<QString, Kopete::Group*>::Iterator it=m_oldGroupList.begin() ; it != m_oldGroupList.end() ; ++it ) + { + Kopete::Group *g=it.data(); + if (g && g->pluginData( protocol(), accountId() + " displayName" ) == groupName && + g->pluginData( protocol(), accountId() + " id" ).isEmpty() ) + { //it has the same name! we got it. (and it is not yet an msn group) + fallBack=g; + /*if ( g->displayName() != groupName ) + { + // The displayName was changed in Kopete while we were offline + // FIXME: update the server right now + }*/ + break; + } + } + + if(!fallBack) + { + //it's certenly a new group ! search if one already exist with the same displayname. + for ( Kopete::Group *g = groupList.first(); g; g = groupList.next() ) + { + /* --This has been replaced by the loop right before. + if ( !g->pluginData( protocol(), accountId() + " id" ).isEmpty() ) + { + if ( g->pluginData( protocol(), accountId() + " id" ).toUInt() == groupNumber ) + { + m_groupList.insert( groupNumber, g ); + QString oldGroupName; + if ( g->pluginData( protocol(), accountId() + " displayName" ) != groupName ) + { + // The displayName of the group has been modified by another client + slotGroupRenamed( groupName, groupNumber ); + } + return; + } + } + else {*/ + if ( g->displayName() == groupName && (groupGuid.isEmpty()|| g->type()==Kopete::Group::Normal) && + g->pluginData( protocol(), accountId() + " id" ).isEmpty() ) + { + fallBack = g; + kdDebug( 14140 ) << k_funcinfo << "We didn't found the group " << groupName <<" in the old MSN group. But kopete has already one with the same name." << endl; + break; + } + } + } + + if ( !fallBack ) + { + if( groupGuid.isEmpty() ) + { // The group #0 is an unremovable group. his default name is "~" , + // but the official client rename it i18n("others contact") at the first + // connection. + // In many case, the users don't use that group as a real group, or just as + // a group to put all contact that are not sorted. + fallBack = Kopete::Group::topLevel(); + } + else + { + fallBack = new Kopete::Group( groupName ); + Kopete::ContactList::self()->addGroup( fallBack ); + kdDebug( 14140 ) << k_funcinfo << "We didn't found the group " << groupName <<" So we're creating a new one." << endl; + + } + } + + fallBack->setPluginData( protocol(), accountId() + " id", groupGuid ); + fallBack->setPluginData( protocol(), accountId() + " displayName", groupName ); + m_groupList.insert( groupGuid, fallBack ); + + // We have pending groups that we need add a contact to + if ( tmp_addToNewGroup.contains(groupName) ) + { + QStringList list=tmp_addToNewGroup[groupName]; + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + QString contactId = *it; + kdDebug( 14140 ) << k_funcinfo << "Adding to new group: " << contactId << endl; + MSNContact *c = static_cast<MSNContact *>(contacts()[contactId]); + if(c && c->hasProperty(MSNProtocol::protocol()->propGuid.key()) ) + notifySocket()->addContact( contactId, MSNProtocol::FL, QString::null, c->guid(), groupGuid ); + else + { + // If we get to here, we're currently adding a new contact, add the groupGUID to the groupList + // to add when contact will be added to contactlist. + if( tmp_addNewContactToGroup.contains( contactId ) ) + tmp_addNewContactToGroup[contactId].append(groupGuid); + else + tmp_addNewContactToGroup.insert(contactId, QStringList(groupGuid) ); + } + } + tmp_addToNewGroup.remove(groupName); + } +} + +void MSNAccount::slotGroupRenamed( const QString &groupGuid, const QString& groupName ) +{ + if ( m_groupList.contains( groupGuid ) ) + { + m_groupList[ groupGuid ]->setPluginData( protocol(), accountId() + " id", groupGuid ); + m_groupList[ groupGuid ]->setPluginData( protocol(), accountId() + " displayName", groupName ); + m_groupList[ groupGuid ]->setDisplayName( groupName ); + } + else + { + slotGroupAdded( groupName, groupGuid ); + } +} + +void MSNAccount::slotGroupRemoved( const QString& groupGuid ) +{ + if ( m_groupList.contains( groupGuid ) ) + { + m_groupList[ groupGuid ]->setPluginData( protocol(), QMap<QString,QString>() ); + m_groupList.remove( groupGuid ); + } +} + +void MSNAccount::addGroup( const QString &groupName, const QString& contactToAdd ) +{ + if ( !contactToAdd.isNull() ) + { + if( tmp_addToNewGroup.contains(groupName) ) + { + tmp_addToNewGroup[groupName].append(contactToAdd); + //A group with the same name is about to be added, + // we don't need to add a second group with the same name + kdDebug( 14140 ) << k_funcinfo << "no need to re-add " << groupName << " for " << contactToAdd << endl; + return; + } + else + { + tmp_addToNewGroup.insert(groupName,QStringList(contactToAdd)); + kdDebug( 14140 ) << k_funcinfo << "preparing to add " << groupName << " for " << contactToAdd << endl; + } + } + + if ( m_notifySocket ) + m_notifySocket->addGroup( groupName ); + +} + +void MSNAccount::slotKopeteGroupRenamed( Kopete::Group *g ) +{ + if ( notifySocket() && g->type() == Kopete::Group::Normal ) + { + if ( !g->pluginData( protocol(), accountId() + " id" ).isEmpty() && + g->displayName() != g->pluginData( protocol(), accountId() + " displayName" ) && + m_groupList.contains( g->pluginData( protocol(), accountId() + " id" ) ) ) + { + notifySocket()->renameGroup( g->displayName(), g->pluginData( protocol(), accountId() + " id" ) ); + } + } +} + +void MSNAccount::slotKopeteGroupRemoved( Kopete::Group *g ) +{ + //The old gorup list is only used whe syncing the contactlist. + //We can assume the contactlist is already fully synced at this time. + //The group g is maybe in the oldGroupList. We remove everithing since + //we don't need it anymore, no need to search it + m_oldGroupList.clear(); + + + if ( !g->pluginData( protocol(), accountId() + " id" ).isEmpty() ) + { + QString groupGuid = g->pluginData( protocol(), accountId() + " id" ); + if ( !m_groupList.contains( groupGuid ) ) + { + // the group is maybe already removed in the server + slotGroupRemoved( groupGuid ); + return; + } + + //this is also done later, but he have to do it now! + // (in slotGroupRemoved) + m_groupList.remove(groupGuid); + + if ( groupGuid.isEmpty() ) + { + // the group #0 can't be deleted + // then we set it as the top-level group + if ( g->type() == Kopete::Group::TopLevel ) + return; + + Kopete::Group::topLevel()->setPluginData( protocol(), accountId() + " id", "" ); + Kopete::Group::topLevel()->setPluginData( protocol(), accountId() + " displayName", g->pluginData( protocol(), accountId() + " displayName" ) ); + g->setPluginData( protocol(), accountId() + " id", QString::null ); // the group should be soon deleted, but make sure + + return; + } + + if ( m_notifySocket ) + { + bool still_have_contact=false; + // if contact are contains only in the group we are removing, abort the + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + MSNContact *c = static_cast<MSNContact *>( it.current() ); + if ( c && c->serverGroups().contains( groupGuid ) ) + { + /** don't do that becasue theses may already have been sent + m_notifySocket->removeContact( c->contactId(), groupNumber, MSNProtocol::FL ); + */ + still_have_contact=true; + break; + } + } + if(!still_have_contact) + m_notifySocket->removeGroup( groupGuid ); + } + } +} + +void MSNAccount::slotNewContactList() +{ + m_oldGroupList=m_groupList; + for(QMap<QString, Kopete::Group*>::Iterator it=m_oldGroupList.begin() ; it != m_oldGroupList.end() ; ++it ) + { //they are about to be changed + if(it.data()) + it.data()->setPluginData( protocol(), accountId() + " id", QString::null ); + } + + m_allowList.clear(); + m_blockList.clear(); + m_reverseList.clear(); + m_groupList.clear(); + KConfigGroup *config=configGroup(); + config->writeEntry( "blockList" , QString::null ) ; + config->writeEntry( "allowList" , QString::null ); + config->writeEntry( "reverseList" , QString::null ); + + // clear all date information which will be received. + // if the information is not anymore on the server, it will not be received + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + MSNContact *c = static_cast<MSNContact *>( *it ); + c->setBlocked( false ); + c->setAllowed( false ); + c->setReversed( false ); + c->setDeleted( true ); + c->setInfo( "PHH", QString::null ); + c->setInfo( "PHW", QString::null ); + c->setInfo( "PHM", QString::null ); + c->removeProperty( MSNProtocol::protocol()->propGuid ); + } + m_newContactList=true; +} + +void MSNAccount::slotContactListed( const QString& handle, const QString& publicName, const QString &contactGuid, uint lists, const QString& groups ) +{ + // On empty lists handle might be empty, ignore that + // ignore also the myself contact. + if ( handle.isEmpty() || handle==accountId()) + return; + + MSNContact *c = static_cast<MSNContact *>( contacts()[ handle ] ); + + if ( lists & 1 ) // FL + { + QStringList contactGroups = QStringList::split( ",", groups, false ); + if ( c ) + { + if( !c->metaContact() ) + { + kdWarning( 14140 ) << k_funcinfo << "the contact " << c->contactId() << " has no meta contact" <<endl; + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + + c->setMetaContact(metaContact); + Kopete::ContactList::self()->addMetaContact( metaContact ); + } + + // Contact exists, update data. + // Merging difference between server contact list and Kopete::Contact's contact list into MetaContact's contact-list + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + if(!publicName.isEmpty() && publicName!=handle) + c->setProperty( Kopete::Global::Properties::self()->nickName() , publicName ); + else + c->removeProperty( Kopete::Global::Properties::self()->nickName() ); + c->setProperty( MSNProtocol::protocol()->propGuid, contactGuid); + + const QMap<QString, Kopete::Group *> oldServerGroups = c->serverGroups(); + c->clearServerGroups(); + for ( QStringList::ConstIterator it = contactGroups.begin(); it != contactGroups.end(); ++it ) + { + QString newServerGroupID = *it; + if(m_groupList.contains(newServerGroupID)) + { + Kopete::Group *newServerGroup=m_groupList[ newServerGroupID ] ; + c->contactAddedToGroup( newServerGroupID, newServerGroup ); + if( !c->metaContact()->groups().contains(newServerGroup) ) + { + // The contact has been added in a group by another client + c->metaContact()->addToGroup( newServerGroup ); + } + } + } + + for ( QMap<QString, Kopete::Group *>::ConstIterator it = oldServerGroups.begin(); it != oldServerGroups.end(); ++it ) + { + Kopete::Group *old_group=m_oldGroupList[it.key()]; + if(old_group) + { + QString oldnewID=old_group->pluginData(protocol() , accountId() +" id"); + if ( !oldnewID.isEmpty() && contactGroups.contains( oldnewID ) ) + continue; //ok, it's correctn no need to do anything. + + c->metaContact()->removeFromGroup( old_group ); + } + } + + c->setDeleted(false); + + // Update server if the contact has been moved to another group while MSN was offline + c->sync(); + } + else + { + Kopete::MetaContact *metaContact = new Kopete::MetaContact(); + + c = new MSNContact( this, handle, metaContact ); + c->setDeleted(true); //we don't want to sync + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + if(!publicName.isEmpty() && publicName!=handle) + c->setProperty( Kopete::Global::Properties::self()->nickName() , publicName ); + else + c->removeProperty( Kopete::Global::Properties::self()->nickName() ); + c->setProperty( MSNProtocol::protocol()->propGuid, contactGuid ); + + for ( QStringList::Iterator it = contactGroups.begin(); + it != contactGroups.end(); ++it ) + { + QString groupGuid = *it; + if(m_groupList.contains(groupGuid)) + { + c->contactAddedToGroup( groupGuid, m_groupList[ groupGuid ] ); + metaContact->addToGroup( m_groupList[ groupGuid ] ); + } + } + Kopete::ContactList::self()->addMetaContact( metaContact ); + + c->setDeleted(false); + } + } + else //the contact is _not_ in the FL, it has been removed + { + if(c) + { + c->setOnlineStatus( static_cast<MSNProtocol*>(protocol())->UNK ); + c->clearServerGroups(); + //TODO: display a message and suggest to remove the contact. + // but i fear a simple messageBox QuestionYesNo here gives a nice crash. + //delete ct; + } + } + if ( lists & 2 ) + slotContactAdded( handle, "AL", publicName, QString::null, QString::null ); + else if(c) + c->setAllowed(false); + if ( lists & 4 ) + slotContactAdded( handle, "BL", publicName, QString::null, QString::null ); + else if(c) + c->setBlocked(false); + if ( lists & 8 ) + slotContactAdded( handle, "RL", publicName, QString::null, QString::null ); + else if(c) + c->setReversed(false); + if ( lists & 16 ) // This contact is on the pending list. Add to the reverse list and delete from the pending list + { + notifySocket()->addContact( handle, MSNProtocol::RL, QString::null, QString::null, QString::null ); + notifySocket()->removeContact( handle, MSNProtocol::PL, QString::null, QString::null ); + } +} + +void MSNAccount::slotContactAdded( const QString& handle, const QString& list, const QString& publicName, const QString& contactGuid, const QString &groupGuid ) +{ + if ( list == "FL" ) + { + bool new_contact = false; + if ( !contacts()[ handle ] ) + { + new_contact = true; + + Kopete::MetaContact *m= m_addWizard_metaContact ? m_addWizard_metaContact : new Kopete::MetaContact(); + + MSNContact *c = new MSNContact( this, handle, m ); + if(!publicName.isEmpty() && publicName!=handle) + c->setProperty( Kopete::Global::Properties::self()->nickName() , publicName ); + else + c->removeProperty( Kopete::Global::Properties::self()->nickName() ); + c->setProperty( MSNProtocol::protocol()->propGuid, contactGuid ); + // Add the new contact to the group he belongs. + if ( tmp_addNewContactToGroup.contains(handle) ) + { + QStringList list = tmp_addNewContactToGroup[handle]; + for ( QStringList::Iterator it = list.begin(); it != list.end(); ++it ) + { + QString groupGuid = *it; + + // If the group didn't exist yet (yay for async operations), don't add the contact to the group + // Let slotGroupAdded do it. + if( m_groupList.contains(groupGuid) ) + { + kdDebug( 14140 ) << k_funcinfo << "Adding " << handle << " to group: " << groupGuid << endl; + notifySocket()->addContact( handle, MSNProtocol::FL, QString::null, contactGuid, groupGuid ); + + c->contactAddedToGroup( groupGuid, m_groupList[ groupGuid ] ); + + m->addToGroup( m_groupList[ groupGuid ] ); + + } + if ( !m_addWizard_metaContact ) + { + Kopete::ContactList::self()->addMetaContact( m ); + } + } + tmp_addNewContactToGroup.remove(handle); + } + + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + + m_addWizard_metaContact = 0L; + } + if ( !new_contact ) + { + // Contact has been added to a group + MSNContact *c = findContactByGuid(contactGuid); + if(c != 0L) + { + // Make sure that the contact has always his contactGUID. + if( !c->hasProperty(MSNProtocol::protocol()->propGuid.key()) ) + c->setProperty( MSNProtocol::protocol()->propGuid, contactGuid ); + + if ( c->onlineStatus() == MSNProtocol::protocol()->UNK ) + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + + if ( c->metaContact() && c->metaContact()->isTemporary() ) + c->metaContact()->setTemporary( false, m_groupList.contains( groupGuid ) ? m_groupList[ groupGuid ] : 0L ); + else + { + if(m_groupList.contains(groupGuid)) + { + if( c->metaContact() ) + c->metaContact()->addToGroup( m_groupList[groupGuid] ); + c->contactAddedToGroup( groupGuid, m_groupList[ groupGuid ] ); + } + } + } + } + + if ( !handle.isEmpty() && !m_allowList.contains( handle ) && !m_blockList.contains( handle ) ) + { + kdDebug(14140) << k_funcinfo << "Trying to add contact to AL. " << endl; + notifySocket()->addContact(handle, MSNProtocol::AL, QString::null, QString::null, QString::null ); + } + } + else if ( list == "BL" ) + { + if ( contacts()[ handle ] ) + static_cast<MSNContact *>( contacts()[ handle ] )->setBlocked( true ); + if ( !m_blockList.contains( handle ) ) + { + m_blockList.append( handle ); + configGroup()->writeEntry( "blockList" , m_blockList ) ; + } + } + else if ( list == "AL" ) + { + if ( contacts()[ handle ] ) + static_cast<MSNContact *>( contacts()[ handle ] )->setAllowed( true ); + if ( !m_allowList.contains( handle ) ) + { + m_allowList.append( handle ); + configGroup()->writeEntry( "allowList" , m_allowList ) ; + } + } + else if ( list == "RL" ) + { + // search for new Contacts + Kopete::Contact *ct=contacts()[ handle ]; + if ( !ct || !ct->metaContact() || ct->metaContact()->isTemporary() ) + { + // Users in the allow list or block list now never trigger the + // 'new user' dialog, which makes it impossible to add those here. + // Not necessarily bad, but the usability effects need more thought + // before I declare it good :- ) + if ( !m_allowList.contains( handle ) && !m_blockList.contains( handle ) ) + { + QString nick; //in most case, the public name is not know + if(publicName!=handle) // so we don't whos it if it is not know + nick=publicName; + Kopete::UI::ContactAddedNotifyDialog *dialog= + new Kopete::UI::ContactAddedNotifyDialog( handle,nick,this, + Kopete::UI::ContactAddedNotifyDialog::InfoButton ); + QObject::connect(dialog,SIGNAL(applyClicked(const QString&)), + this,SLOT(slotContactAddedNotifyDialogClosed(const QString& ))); + dialog->show(); + } + } + else + { + static_cast<MSNContact *>( ct )->setReversed( true ); + } + m_reverseList.append( handle ); + configGroup()->writeEntry( "reverseList" , m_reverseList ) ; + } +} + +void MSNAccount::slotContactRemoved( const QString& handle, const QString& list, const QString& contactGuid, const QString& groupGuid ) +{ + kdDebug( 14140 ) << k_funcinfo << "handle: " << handle << " list: " << list << " contact-uid: " << contactGuid << endl; + MSNContact *c=static_cast<MSNContact *>( contacts()[ handle ] ); + if ( list == "BL" ) + { + m_blockList.remove( handle ); + configGroup()->writeEntry( "blockList" , m_blockList ) ; + if ( !m_allowList.contains( handle ) ) + notifySocket()->addContact( handle, MSNProtocol::AL, QString::null, QString::null, QString::null ); + + if(c) + c->setBlocked( false ); + } + else if ( list == "AL" ) + { + m_allowList.remove( handle ); + configGroup()->writeEntry( "allowList" , m_allowList ) ; + if ( !m_blockList.contains( handle ) ) + notifySocket()->addContact( handle, MSNProtocol::BL, QString::null, QString::null, QString::null ); + + if(c) + c->setAllowed( false ); + } + else if ( list == "RL" ) + { + m_reverseList.remove( handle ); + configGroup()->writeEntry( "reverseList" , m_reverseList ) ; + + if ( c ) + { + // Contact is removed from the reverse list + // only MSN can do this, so this is currently not supported + c->setReversed( false ); + /* + InfoWidget *info = new InfoWidget( 0 ); + info->title->setText( "<b>" + i18n( "Contact removed!" ) +"</b>" ); + QString dummy; + dummy = "<center><b>" + imContact->getPublicName() + "( " +imContact->getHandle() +" )</b></center><br>"; + dummy += i18n( "has removed you from his contact list!" ) + "<br>"; + dummy += i18n( "This contact is now removed from your contact list" ); + info->infoText->setText( dummy ); + info->setCaption( "KMerlin - Info" ); + info->show(); + */ + } + } + else if ( list == "FL" ) + { + // The FL list only use the contact GUID, use the contact referenced by the GUID. + MSNContact *contactRemoved = findContactByGuid(contactGuid); + QStringList groupGuidList; + bool deleteContact = groupGuid.isEmpty() ? true : false; // Delete the contact when the group GUID is empty. + // Remove the contact from the contact list for all the group he is a member. + if( groupGuid.isEmpty() ) + { + if(contactRemoved) + { + QPtrList<Kopete::Group> groupList = contactRemoved->metaContact()->groups(); + for( QPtrList<Kopete::Group>::Iterator it = groupList.begin(); it != groupList.end(); ++it ) + { + Kopete::Group *group = *it; + if ( !group->pluginData( protocol(), accountId() + " id" ).isEmpty() ) + { + groupGuidList.append( group->pluginData( protocol(), accountId() + " id" ) ); + } + } + } + } + else + { + groupGuidList.append( groupGuid ); + } + + if( !groupGuidList.isEmpty() ) + { + QStringList::const_iterator stringIt; + for( stringIt = groupGuidList.begin(); stringIt != groupGuidList.end(); ++stringIt ) + { + // Contact is removed from the FL list, remove it from the group + if(contactRemoved != 0L) + contactRemoved->contactRemovedFromGroup( *stringIt ); + + //check if the group is now empty to remove it + if ( m_notifySocket ) + { + bool still_have_contact=false; + // if contact are contains only in the group we are removing, abort the + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + MSNContact *c2 = static_cast<MSNContact *>( it.current() ); + if ( c2->serverGroups().contains( *stringIt ) ) + { + still_have_contact=true; + break; + } + } + if(!still_have_contact) + m_notifySocket->removeGroup( *stringIt ); + } + } + } + if(deleteContact && contactRemoved) + { + kdDebug(14140) << k_funcinfo << "Deleting the MSNContact " << contactRemoved->contactId() << endl; + contactRemoved->deleteLater(); + } + } +} + +void MSNAccount::slotCreateChat( const QString& address, const QString& auth ) +{ + slotCreateChat( 0L, address, auth, m_msgHandle.first(), m_msgHandle.first() ); +} + +void MSNAccount::slotCreateChat( const QString& ID, const QString& address, const QString& auth, + const QString& handle_, const QString& publicName ) +{ + QString handle = handle_.lower(); + + if ( handle.isEmpty() ) + { + // we have lost the handle? + kdDebug(14140) << k_funcinfo << "Impossible to open a chat session, I forgot the contact to invite" <<endl; + // forget it + return; + } + +// kdDebug( 14140 ) << k_funcinfo <<"Creating chat for " << handle << endl; + + if ( !contacts()[ handle ] ) + addContact( handle, publicName, 0L, Kopete::Account::Temporary ); + + MSNContact *c = static_cast<MSNContact *>( contacts()[ handle ] ); + + if ( c && myself() ) + { + // we can't use simply c->manager(true) here to get the manager, because this will re-open + // another chat session, and then, close this new one. We have to re-create the manager manualy. + MSNChatSession *manager = dynamic_cast<MSNChatSession*>( c->manager( Kopete::Contact::CannotCreate ) ); + if(!manager) + { + Kopete::ContactPtrList chatmembers; + chatmembers.append(c); + manager = new MSNChatSession( protocol(), myself(), chatmembers ); + } + + manager->createChat( handle, address, auth, ID ); + + /** + * This code should open a chatwindow when a socket is open + * It has been disabled because gaim open switchboeard too often + * + * the solution is to open the window only when the contact start typing + * see MSNChatSession::receivedTypingMsg + * + + KGlobal::config()->setGroup( "MSN" ); + bool notifyNewChat = KGlobal::config()->readBoolEntry( "NotifyNewChat", false ); + if ( !ID.isEmpty() && notifyNewChat ) + { + // this temporary message should open the window if they not exist + QString body = i18n( "%1 has started a chat with you" ).arg( c->metaContact()->displayName() ); + Kopete::Message tmpMsg = Kopete::Message( c, manager->members(), body, Kopete::Message::Internal, Kopete::Message::PlainText ); + manager->appendMessage( tmpMsg ); + } + */ + } + + if(!m_msgHandle.isEmpty()) + m_msgHandle.pop_front(); +} + +void MSNAccount::slotStartChatSession( const QString& handle ) +{ + // First create a message manager, because we might get an existing + // manager back, in which case we likely also have an active switchboard + // connection to reuse... + + MSNContact *c = static_cast<MSNContact *>( contacts()[ handle ] ); + // if ( isConnected() && c && myself() && handle != m_msnId ) + if ( m_notifySocket && c && myself() && handle != accountId() ) + { + if ( !c->manager(Kopete::Contact::CannotCreate) || !static_cast<MSNChatSession *>( c->manager( Kopete::Contact::CanCreate ) )->service() ) + { + m_msgHandle.prepend(handle); + m_notifySocket->createChatSession(); + } + } +} + +void MSNAccount::slotContactAddedNotifyDialogClosed(const QString& handle) +{ + const Kopete::UI::ContactAddedNotifyDialog *dialog = + dynamic_cast<const Kopete::UI::ContactAddedNotifyDialog *>(sender()); + if(!dialog || !m_notifySocket) + return; + + if(dialog->added()) + { + Kopete::MetaContact *mc=dialog->addContact(); + if(mc) + { //if the contact has been added this way, it's because the other user added us. + // don't forgot to set the reversed flag (Bug 114400) + MSNContact *c=dynamic_cast<MSNContact*>(mc->contacts().first()); + if(c && c->contactId() == handle ) + { + c->setReversed( true ); + } + } + } + + if ( !dialog->authorized() ) + { + if ( m_allowList.contains( handle ) ) + m_notifySocket->removeContact( handle, MSNProtocol::AL, QString::null, QString::null ); + else if ( !m_blockList.contains( handle ) ) + m_notifySocket->addContact( handle, MSNProtocol::BL, QString::null, QString::null, QString::null ); + } + else + { + if ( m_blockList.contains( handle ) ) + m_notifySocket->removeContact( handle, MSNProtocol::BL, QString::null, QString::null ); + else if ( !m_allowList.contains( handle ) ) + m_notifySocket->addContact( handle, MSNProtocol::AL, QString::null, QString::null, QString::null ); + } + + +} + +void MSNAccount::slotGlobalIdentityChanged( const QString &key, const QVariant &value ) +{ + if( !configGroup()->readBoolEntry("ExcludeGlobalIdentity", false) ) + { + if(key == Kopete::Global::Properties::self()->nickName().key()) + { + QString oldNick = myself()->property( Kopete::Global::Properties::self()->nickName()).value().toString(); + QString newNick = value.toString(); + + if(newNick != oldNick) + { + setPublicName( value.toString() ); + } + } + else if(key == Kopete::Global::Properties::self()->photo().key()) + { + m_pictureFilename = value.toString(); + kdDebug( 14140 ) << k_funcinfo << m_pictureFilename << endl; + resetPictureObject(false, true); + } + } +} + +void MSNAccount::slotErrorMessageReceived( int type, const QString &msg ) +{ + QString caption = i18n( "MSN Plugin" ); + + // Use different notification type based on the error context. + switch(type) + { + case MSNSocket::ErrorConnectionLost: + { + Kopete::Utils::notifyConnectionLost( this, caption, msg ); + break; + } + case MSNSocket::ErrorConnectionError: + { + Kopete::Utils::notifyConnectionError( this, caption, msg ); + break; + } + case MSNSocket::ErrorCannotConnect: + { + Kopete::Utils::notifyCannotConnect( this ); + break; + } + case MSNSocket::ErrorInformation: + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, msg, caption ); + break; + } + case MSNSocket::ErrorServerError: + default: + { + Kopete::Utils::notifyServerError( this, caption, msg ); + break; + } + } +} + +bool MSNAccount::createContact( const QString &contactId, Kopete::MetaContact *metaContact ) +{ + if ( !metaContact->isTemporary() && m_notifySocket) + { + m_addWizard_metaContact = metaContact; + + addContactServerside(contactId, metaContact->groups()); + + // FIXME: Find out if this contact was really added or not! + return true; + } + else + { + // This is a temporary contact. ( a person who messaged us but is not on our conntact list. + // We don't want to create it on the server.Just create the local contact object and add it + // Or we are diconnected, and in that case, the contact will be added when connecting + MSNContact *newContact = new MSNContact( this, contactId, metaContact ); + newContact->setDeleted(true); + return true; + } + +} + +void MSNAccount::addContactServerside(const QString &contactId, QPtrList<Kopete::Group> groupList) +{ + // First of all, fill the temporary group list. The contact will be moved to his group(s). + // When we receive back his contact GUID(required to move a contact between groups) + for( Kopete::Group *group = groupList.first(); group; group = groupList.next() ) + { + // TODO: It it time that libkopete generate a unique ID that contains protocols, account and contact id. + QString groupId = group->pluginData( protocol(), accountId() + " id" ); + // If the groupId is empty, that's mean the Kopete group is not on the MSN server. + if( !groupId.isEmpty() ) + { + // Something got corrupted on contactlist.xml + if( !m_groupList.contains(groupId) ) + { + // Clear the group plugin data. + group->setPluginData( protocol() , accountId() + " id" , QString::null); + group->setPluginData( protocol() , accountId() + " displayName" , QString::null); + kdDebug( 14140 ) << k_funcinfo << " Group " << group->displayName() << " marked with id #" << groupId << " does not seems to be anymore on the server" << endl; + + // Add the group on MSN server, will fix the corruption. + kdDebug(14140) << k_funcinfo << "Fixing group corruption, re-adding " << group->displayName() << "to the server." << endl; + addGroup( group->displayName(), contactId); + } + else + { + // Add the group that the contact belong to add it when we will receive the contact GUID. + if( tmp_addNewContactToGroup.contains( contactId ) ) + tmp_addNewContactToGroup[contactId].append(groupId); + else + tmp_addNewContactToGroup.insert(contactId, QStringList(groupId) ); + } + } + else + { + if( !group->displayName().isEmpty() && group->type() == Kopete::Group::Normal ) + { + kdDebug(14140) << k_funcinfo << "Group not on MSN server, add it" << endl; + addGroup( group->displayName(), contactId ); + } + } + } + + // After add the contact to the top-level, it will be moved to required groups later. + kdDebug( 14140 ) << k_funcinfo << "Add the contact on the server " << endl; + m_notifySocket->addContact( contactId, MSNProtocol::FL, contactId, QString::null, QString::null ); +} + +MSNContact *MSNAccount::findContactByGuid(const QString &contactGuid) +{ + kdDebug(14140) << k_funcinfo << "Looking for " << contactGuid << endl; + QDictIterator<Kopete::Contact> it( contacts() ); + for ( ; it.current(); ++it ) + { + MSNContact *c = dynamic_cast<MSNContact *>( it.current() ); + + if(c && c->guid() == contactGuid ) + { + kdDebug(14140) << k_funcinfo << "OK found a contact. " << endl; + // Found the contact GUID + return c; + } + } + + return 0L; +} + +bool MSNAccount::isHotmail() const +{ + if ( !m_openInboxAction ) + return false; + return m_openInboxAction->isEnabled(); +} + +QString MSNAccount::pictureUrl() +{ + return m_pictureFilename; +} + +void MSNAccount::setPictureUrl(const QString &url) +{ + m_pictureFilename = url; +} + +QString MSNAccount::pictureObject() +{ + if(m_pictureObj.isNull()) + resetPictureObject(true); //silent=true to keep infinite loop away + return m_pictureObj; +} + +void MSNAccount::resetPictureObject(bool silent, bool force) +{ + QString old=m_pictureObj; + + if(!configGroup()->readBoolEntry("exportCustomPicture") && !force) + { + m_pictureObj=""; + myself()->removeProperty( Kopete::Global::Properties::self()->photo() ); + } + else + { + // Check if the picture is a 96x96 image, if not scale, crop and save. + QImage picture(m_pictureFilename); + if(picture.isNull()) + { + m_pictureObj=""; + myself()->removeProperty( Kopete::Global::Properties::self()->photo() ); + } + else + { + if(picture.width() != 96 || picture.height() != 96) + { + // Save to a new location in msnpictures. + QString newLocation( locateLocal( "appdata", "msnpictures/"+ KURL(m_pictureFilename).fileName().lower() ) ); + + // Scale and crop the picture. + picture = MSNProtocol::protocol()->scalePicture(picture); + + // Use the cropped/scaled image now. + if(!picture.save(newLocation, "PNG")) + { + m_pictureObj=""; + myself()->removeProperty( Kopete::Global::Properties::self()->photo() ); + } + m_pictureFilename = newLocation; + } + } + + QFile pictFile( m_pictureFilename ); + if(!pictFile.open(IO_ReadOnly)) + { + m_pictureObj=""; + myself()->removeProperty( Kopete::Global::Properties::self()->photo() ); + } + else + { + QByteArray ar=pictFile.readAll(); + QString sha1d= QString((KCodecs::base64Encode(SHA1::hash(ar)))); + + QString size=QString::number( pictFile.size() ); + QString all= "Creator"+accountId()+"Size"+size+"Type3Locationkopete.tmpFriendlyAAA=SHA1D"+ sha1d; + m_pictureObj="<msnobj Creator=\"" + accountId() + "\" Size=\"" + size + "\" Type=\"3\" Location=\"kopete.tmp\" Friendly=\"AAA=\" SHA1D=\""+sha1d+"\" SHA1C=\""+ QString(KCodecs::base64Encode(SHA1::hashString(all.utf8()))) +"\"/>"; + myself()->setProperty( Kopete::Global::Properties::self()->photo() , m_pictureFilename ); + } + } + + if(old!=m_pictureObj && isConnected() && m_notifySocket && !silent) + { + //update the msn pict + m_notifySocket->setStatus( myself()->onlineStatus() ); + } +} + +#include "msnaccount.moc" + +// vim: set noet ts=4 sts=4 sw=4: + + diff --git a/kopete/protocols/msn/msnaccount.h b/kopete/protocols/msn/msnaccount.h new file mode 100644 index 00000000..7fbee16b --- /dev/null +++ b/kopete/protocols/msn/msnaccount.h @@ -0,0 +1,270 @@ +/* + msnaccount.h - Manages a single MSN account + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Michaêl Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2003-2005 by The Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNACCOUNT_H +#define MSNACCOUNT_H + +#include <qobject.h> + +#include "kopetepasswordedaccount.h" + +#include "msnprotocol.h" + +class KAction; +class KActionMenu; + +class MSNNotifySocket; +class MSNContact; + +/** + * MSNAccount encapsulates everything that is account-based, as opposed to + * protocol based. This basically means sockets, current status, and account + * info are stored in the account, whereas the protocol is only the + * 'manager' class that creates and manages accounts. + */ +class MSNAccount : public Kopete::PasswordedAccount +{ + Q_OBJECT + +public: + MSNAccount( MSNProtocol *parent, const QString &accountID, const char *name = 0L ); + + /* + * return the menu for this account + */ + virtual KActionMenu* actionMenu(); + + //------ internal functions + /** + * change the publicName to this new name + */ + void setPublicName( const QString &name ); + void setPersonalMessage(MSNProtocol::PersonalMessageType type, const QString &personalMessage ); + + /** + * Returns the address of the MSN server + */ + QString serverName(); + + /** + * Returns the address of the MSN server port + */ + uint serverPort(); + + MSNNotifySocket *notifySocket(); + + /** + * return true if we are able to send mail, or to open hotmail inbox + */ + bool isHotmail() const; + + + /** + * Return the picture url. + */ + QString pictureUrl(); + + /** + * Set the picture url. + */ + void setPictureUrl(const QString &url); + + /** + * return the <msnobj> tag of the display picture + */ + QString pictureObject(); + + /** + * reset the <msnobj>. This method should be called if the displayimage has changed + * If we are actualy connected, it will imediatly update the <msgobj> on the server, exepted + * if @param silent is set to true + * @param force Force the application of MSN picture + */ + void resetPictureObject(bool silent=false, bool force=false); + + //BEGIN Http + + bool useHttpMethod() const; + + //END + + /** + * Return the client ID for the myself contact of this account. + * It is dynamic to see if we really have a webcam or not. + */ + QString myselfClientId() const; + +public slots: + virtual void connectWithPassword( const QString &password ) ; + virtual void disconnect() ; + virtual void setOnlineStatus( const Kopete::OnlineStatus &status , const QString &reason = QString::null); + + /** + * Ask to the account to create a new chat session + */ + void slotStartChatSession( const QString& handle ); + + /** + * Single slot to display error message. + */ + void slotErrorMessageReceived( int type, const QString &msg ); + +protected: + virtual bool createContact( const QString &contactId, Kopete::MetaContact *parentContact ); + + +private slots: + // Actions related + void slotStartChat(); + void slotOpenInbox(); + void slotChangePublicName(); + +//#if !defined NDEBUG //(Stupid moc which don't see when he don't need to slot this slot) + /** + * Show simple debugging aid + */ + void slotDebugRawCommand(); +//#endif + + // notifySocket related + void slotStatusChanged( const Kopete::OnlineStatus &status ); + void slotNotifySocketClosed(); + void slotPersonalMessageChanged(const QString& personalMessage); + void slotContactRemoved(const QString& handle, const QString& list, const QString& contactGuid, const QString& groupGuid ); + void slotContactAdded(const QString& handle, const QString& list, const QString& publicName, const QString& contactGuid, const QString &groupGuid ); + void slotContactListed( const QString& handle, const QString& publicName, const QString &contactGuid, uint lists, const QString& groups ); + void slotNewContactList(); + /** + * The group has successful renamed in the server + * groupName: is new new group name + */ + void slotGroupRenamed(const QString &groupGuid, const QString& groupName ); + /** + * A new group was created on the server (or received durring an LSG command) + */ + void slotGroupAdded( const QString& groupName, const QString &groupGuid ); + /** + * Group was removed from the server + */ + void slotGroupRemoved( const QString &groupGuid ); + /** + * Incoming RING command: connect to the Switchboard server and send + * the startChat signal + */ + void slotCreateChat( const QString& sessionID, const QString& address, const QString& auth, + const QString& handle, const QString& publicName ); + /** + * Incoming XFR command: this is an result from + * slotStartChatSession(handle) + * connect to the switchboard server and sen startChat signal + */ + void slotCreateChat( const QString& address, const QString& auth); + + + // ui related + /** + * A kopetegroup is renamed, rename group on the server + */ + void slotKopeteGroupRenamed( Kopete::Group *g ); + + /** + * A kopetegroup is removed, remove the group in the server + **/ + void slotKopeteGroupRemoved( Kopete::Group* ); + + /** + * add contact ui + */ + void slotContactAddedNotifyDialogClosed( const QString &handle); + + /** + * When the dispatch server sends us the notification server to use. + */ + void createNotificationServer( const QString &host, uint port ); + + /** + * When a global identity key get changed. + */ + void slotGlobalIdentityChanged( const QString &key, const QVariant &value ); + +private: + MSNNotifySocket *m_notifySocket; + KAction *m_openInboxAction; + KAction *m_startChatAction; + KAction *m_changeDNAction; + + // status which will be using for connecting + Kopete::OnlineStatus m_connectstatus; + + QStringList m_msgHandle; + + bool m_newContactList; + + + /** + * Add the contact on the server in the given groups. + * this is a helper function called bu createContact and slotStatusChanged + */ + void addContactServerside(const QString &contactId, QPtrList<Kopete::Group> groupList); + + + +public: //FIXME: should be private + QMap<QString, Kopete::Group*> m_groupList; + + void addGroup( const QString &groupName, const QString &contactToAdd = QString::null ); + + /** + * Find and retrive a MSNContact by its contactGuid. (Helper function) + */ + MSNContact *findContactByGuid(const QString &contactGuid); +private: + + // server data + QStringList m_allowList; + QStringList m_blockList; + QStringList m_reverseList; + + Kopete::MetaContact *m_addWizard_metaContact; + QMap< QString, QStringList > tmp_addToNewGroup; + QMap< QString, QStringList > tmp_addNewContactToGroup; + + QString m_pictureObj; //a cache of the <msnobj> + QString m_pictureFilename; // the picture filename. + + //this is the translation between old to new groups id when syncing from server. + QMap<QString, Kopete::Group*> m_oldGroupList; + + /** + * I need the password in createNotificationServer. + * but i can't ask it there with password() because a nested loop will provoque crash + * at this place. so i'm forced to keep it here. + * I would like an API to request the password WITHOUT askling it. + */ + QString m_password; + + /** + * Cliend ID is a bitfield that contains supported features for a MSN contact. + */ + uint m_clientId; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnaddcontactpage.cpp b/kopete/protocols/msn/msnaddcontactpage.cpp new file mode 100644 index 00000000..337939e6 --- /dev/null +++ b/kopete/protocols/msn/msnaddcontactpage.cpp @@ -0,0 +1,85 @@ +/* + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart@ kde.org> + Kopete (c) 2002-2005 by The Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include <qlayout.h> +#include <qlineedit.h> + +#include <klocale.h> +#include <kmessagebox.h> + +#include "msnadd.h" +#include "msnaddcontactpage.h" +#include "msnprotocol.h" +#include "kopeteaccount.h" +#include "kopeteuiglobal.h" + +MSNAddContactPage::MSNAddContactPage(bool connected, QWidget *parent, const char *name ) + : AddContactPage(parent,name) +{ + (new QVBoxLayout(this))->setAutoAdd(true); +/* if ( connected ) + {*/ + msndata = new msnAddUI(this); + /* + msndata->cmbGroup->insertStringList(owner->getGroups()); + msndata->cmbGroup->setCurrentItem(0); + */ + canadd = true; + +/* } + else + { + noaddMsg1 = new QLabel( i18n( "You need to be connected to be able to add contacts." ), this ); + noaddMsg2 = new QLabel( i18n( "Please connect to the MSN network and try again." ), this ); + canadd = false; +}*/ + +} +MSNAddContactPage::~MSNAddContactPage() +{ +} + +bool MSNAddContactPage::apply( Kopete::Account* i, Kopete::MetaContact*m ) +{ + if ( validateData() ) + { + QString userid = msndata->addID->text(); + return i->addContact( userid , m, Kopete::Account::ChangeKABC ); + } + return false; +} + + +bool MSNAddContactPage::validateData() +{ + if(!canadd) + return false; + + QString userid = msndata->addID->text(); + + if(MSNProtocol::validContactId(userid)) + return true; + + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>You must enter a valid email address.</qt>" ), i18n( "MSN Plugin" ) ); + + return false; + +} + +#include "msnaddcontactpage.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnaddcontactpage.h b/kopete/protocols/msn/msnaddcontactpage.h new file mode 100644 index 00000000..c2e3fb3b --- /dev/null +++ b/kopete/protocols/msn/msnaddcontactpage.h @@ -0,0 +1,33 @@ + +#ifndef MSNADDCONTACTPAGE_H +#define MSNADDCONTACTPAGE_H + +#include <qwidget.h> +#include <addcontactpage.h> +#include <qlabel.h> + +/** + *@author duncan + */ +class msnAddUI; +class MSNProtocol; + +class MSNAddContactPage : public AddContactPage +{ + Q_OBJECT +public: + MSNAddContactPage(bool connected, QWidget *parent=0, const char *name=0); + ~MSNAddContactPage(); + msnAddUI *msndata; + QLabel *noaddMsg1; + QLabel *noaddMsg2; + bool canadd; + virtual bool validateData(); + virtual bool apply( Kopete::Account*, Kopete::MetaContact* ); + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnchallengehandler.cpp b/kopete/protocols/msn/msnchallengehandler.cpp new file mode 100644 index 00000000..e0bcc987 --- /dev/null +++ b/kopete/protocols/msn/msnchallengehandler.cpp @@ -0,0 +1,151 @@ +/* + msnchallengehandler.h - Computes a msn challenge response hash key. + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + Kopete (c) 2003-2005 by The Kopete developers <kopete-devel@kde.org> + + Portions taken from + http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnchallengehandler.h" + +#include <qdatastream.h> + +#include <kdebug.h> +#include <kmdcodec.h> + +MSNChallengeHandler::MSNChallengeHandler(const QString& productKey, const QString& productId) +{ + m_productKey = productKey; + m_productId = productId; +} + + +MSNChallengeHandler::~MSNChallengeHandler() +{ + kdDebug(14140) << k_funcinfo << endl; +} + +QString MSNChallengeHandler::computeHash(const QString& challengeString) +{ + // Step One: THe MD5 Hash. + + // Combine the received challenge string with the product key. + KMD5 md5((challengeString + m_productKey).utf8()); + QCString digest = md5.hexDigest(); + + kdDebug(14140) << k_funcinfo << "md5: " << digest << endl; + + QValueVector<Q_INT32> md5Integers(4); + for(Q_UINT32 i=0; i < md5Integers.count(); i++) + { + md5Integers[i] = hexSwap(digest.mid(i*8, 8)).toUInt(0, 16) & 0x7FFFFFFF; + kdDebug(14140) << k_funcinfo << ("0x" + hexSwap(digest.mid(i*8, 8))) << " " << md5Integers[i] << endl; + } + + // Step Two: Create the challenge string key + + QString challengeKey = challengeString + m_productId; + // Pad to multiple of 8. + challengeKey = challengeKey.leftJustify(challengeKey.length() + (8 - challengeKey.length() % 8), '0'); + + kdDebug(14140) << k_funcinfo << "challenge key: " << challengeKey << endl; + + QValueVector<Q_INT32> challengeIntegers(challengeKey.length() / 4); + for(Q_UINT32 i=0; i < challengeIntegers.count(); i++) + { + QString sNum = challengeKey.mid(i*4, 4), sNumHex; + + // Go through the number string, determining the hex equivalent of each value + // and add that to our new hex string for this number. + for(uint j=0; j < sNum.length(); j++) { + sNumHex += QString::number((int)sNum[j].latin1(), 16); + } + + // swap because of the byte ordering issue. + sNumHex = hexSwap(sNumHex); + // Assign the converted number. + challengeIntegers[i] = sNumHex.toInt(0, 16); + kdDebug(14140) << k_funcinfo << sNum << (": 0x"+sNumHex) << " " << challengeIntegers[i] << endl; + } + + // Step Three: Create the 64-bit hash key. + + // Get the hash key using the specified arrays. + Q_INT64 key = createHashKey(md5Integers, challengeIntegers); + kdDebug(14140) << k_funcinfo << "key: " << key << endl; + + // Step Four: Create the final hash key. + + QString upper = QString::number(QString(digest.mid(0, 16)).toULongLong(0, 16)^key, 16); + if(upper.length() % 16 != 0) + upper = upper.rightJustify(upper.length() + (16 - upper.length() % 16), '0'); + + QString lower = QString::number(QString(digest.mid(16, 16)).toULongLong(0, 16)^key, 16); + if(lower.length() % 16 != 0) + lower = lower.rightJustify(lower.length() + (16 - lower.length() % 16), '0'); + + return (upper + lower); +} + +Q_INT64 MSNChallengeHandler::createHashKey(const QValueVector<Q_INT32>& md5Integers, + const QValueVector<Q_INT32>& challengeIntegers) +{ + kdDebug(14140) << k_funcinfo << "Creating 64-bit key." << endl; + + Q_INT64 magicNumber = 0x0E79A9C1L, high = 0L, low = 0L; + + for(uint i=0; i < challengeIntegers.count(); i += 2) + { + Q_INT64 temp = ((challengeIntegers[i] * magicNumber) % 0x7FFFFFFF) + high; + temp = ((temp * md5Integers[0]) + md5Integers[1]) % 0x7FFFFFFF; + + high = (challengeIntegers[i + 1] + temp) % 0x7FFFFFFF; + high = ((high * md5Integers[2]) + md5Integers[3]) % 0x7FFFFFFF; + + low += high + temp; + } + + high = (high + md5Integers[1]) % 0x7FFFFFFF; + low = (low + md5Integers[3]) % 0x7FFFFFFF; + + QDataStream buffer(QByteArray(8), IO_ReadWrite); + buffer.setByteOrder(QDataStream::LittleEndian); + buffer << (Q_INT32)high; + buffer << (Q_INT32)low; + + buffer.device()->reset(); + buffer.setByteOrder(QDataStream::BigEndian); + Q_INT64 key; + buffer >> key; + + return key; +} + +QString MSNChallengeHandler::hexSwap(const QString& in) +{ + QString sHex = in, swapped; + while(sHex.length() > 0) + { + swapped = swapped + sHex.mid(sHex.length() - 2, 2); + sHex = sHex.remove(sHex.length() - 2, 2); + } + return swapped; +} + +QString MSNChallengeHandler::productId() +{ + return m_productId; +} + +#include "msnchallengehandler.moc" diff --git a/kopete/protocols/msn/msnchallengehandler.h b/kopete/protocols/msn/msnchallengehandler.h new file mode 100644 index 00000000..9e866560 --- /dev/null +++ b/kopete/protocols/msn/msnchallengehandler.h @@ -0,0 +1,64 @@ +/* + msnchallengehandler.h - Computes a msn challenge response hash key. + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + Kopete (c) 2003-2005 by The Kopete developers <kopete-devel@kde.org> + + Portions taken from + http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNCHALLENGEHANDLER_H +#define MSNCHALLENGEHANDLER_H + +#include <qobject.h> +#include <qvaluevector.h> + +/** + * Provides a simple way to compute a msn challenge response hash key. + * + * @author Gregg Edghill + */ +class MSNChallengeHandler : public QObject +{ +Q_OBJECT +public: + MSNChallengeHandler(const QString& productKey, const QString& productId); + ~MSNChallengeHandler(); + + /** + * Computes the response hash string for the specified challenge string. + */ + QString computeHash(const QString& challengeString); + + /** + * Returns the product id used by the challenge handler. + */ + QString productId(); + +private: + + /** + * Creates a 64-bit hash key. + */ + Q_INT64 createHashKey(const QValueVector<Q_INT32>& md5Integers, const QValueVector<Q_INT32>& challengeIntegers); + + /** + * Swaps the bytes in a hex string. + */ + QString hexSwap(const QString& in); + + QString m_productKey; + QString m_productId; +}; + +#endif diff --git a/kopete/protocols/msn/msnchatsession.cpp b/kopete/protocols/msn/msnchatsession.cpp new file mode 100644 index 00000000..3bf5d0c6 --- /dev/null +++ b/kopete/protocols/msn/msnchatsession.cpp @@ -0,0 +1,775 @@ +/* + msnchatsession.cpp - MSN Message Manager + + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnchatsession.h" + +#include <qlabel.h> +#include <qimage.h> +#include <qtooltip.h> +#include <qfile.h> +#include <qiconset.h> + + +#include <kconfig.h> +#include <kdebug.h> +#include <kinputdialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kpopupmenu.h> +#include <ktempfile.h> +#include <kmainwindow.h> +#include <ktoolbar.h> +#include <krun.h> + +#include "kopetecontactaction.h" +#include "kopetemetacontact.h" +#include "kopetecontactlist.h" +#include "kopetechatsessionmanager.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" +#include "kopeteview.h" + +#include "msncontact.h" +#include "msnfiletransfersocket.h" +#include "msnaccount.h" +#include "msnswitchboardsocket.h" + +#include "config.h" + +#if !defined NDEBUG +#include "msndebugrawcmddlg.h" +#endif + +MSNChatSession::MSNChatSession( Kopete::Protocol *protocol, const Kopete::Contact *user, + Kopete::ContactPtrList others, const char *name ) +: Kopete::ChatSession( user, others, protocol, name ) +{ + Kopete::ChatSessionManager::self()->registerChatSession( this ); + m_chatService = 0l; + m_timeoutTimer =0L; + m_newSession = true; + m_connectionTry=0; + + setInstance(protocol->instance()); + + connect( this, SIGNAL( messageSent( Kopete::Message&, + Kopete::ChatSession* ) ), + this, SLOT( slotMessageSent( Kopete::Message&, + Kopete::ChatSession* ) ) ); + + connect( this, SIGNAL( invitation(MSNInvitation*& , const QString & , long unsigned int , MSNChatSession* , MSNContact* ) ) , + protocol, SIGNAL( invitation(MSNInvitation*& , const QString & , long unsigned int , MSNChatSession* , MSNContact* ) ) ); + + + m_actionInvite = new KActionMenu( i18n( "&Invite" ), "kontact_contacts", actionCollection(), "msnInvite" ); + connect ( m_actionInvite->popupMenu() , SIGNAL( aboutToShow() ) , this , SLOT(slotActionInviteAboutToShow() ) ) ; + + #if !defined NDEBUG + new KAction( i18n( "Send Raw C&ommand..." ), 0, this, SLOT( slotDebugRawCommand() ), actionCollection(), "msnDebugRawCommand" ) ; + #endif + + + m_actionNudge=new KAction( i18n( "Send Nudge" ), "bell", 0, this, SLOT(slotSendNudge() ), actionCollection(), "msnSendNudge" ) ; +#if MSN_WEBCAM + // Invite to receive webcam action + m_actionWebcamReceive=new KAction( i18n( "View Contact's Webcam" ), "webcamreceive", 0, this, SLOT(slotWebcamReceive()), actionCollection(), "msnWebcamReceive" ) ; + + //Send webcam action + m_actionWebcamSend=new KAction( i18n( "Send Webcam" ), "webcamsend", 0, this, SLOT(slotWebcamSend()), actionCollection(), "msnWebcamSend" ) ; +#endif + + new KAction( i18n( "Send File" ),"attach", 0, this, SLOT( slotSendFile() ), actionCollection(), "msnSendFile" ); + + MSNContact *c = static_cast<MSNContact*>( others.first() ); + (new KAction( i18n( "Request Display Picture" ), "image", 0, this, SLOT( slotRequestPicture() ), actionCollection(), "msnRequestDisplayPicture" ))->setEnabled(!c->object().isEmpty()); + + if ( !c->object().isEmpty() ) + { + + connect( c, SIGNAL( displayPictureChanged() ), this, SLOT( slotDisplayPictureChanged() ) ); + m_image = new QLabel( 0L, "kde toolbar widget" ); + new KWidgetAction( m_image, i18n( "MSN Display Picture" ), 0, this, SLOT( slotRequestPicture() ), actionCollection(), "msnDisplayPicture" ); + if(c->hasProperty(Kopete::Global::Properties::self()->photo().key()) ) + { + //if the view doesn't exist yet, we will be unable to get the size of the toolbar + // so when the view will exist, we will show the displaypicture. + //How to know when a our view is created? We can't. + // but chances are the next created view will be for this KMM + // And if it is not? never mind. the icon will just be sized 22x22 + connect( Kopete::ChatSessionManager::self() , SIGNAL(viewActivated(KopeteView* )) , this, SLOT(slotDisplayPictureChanged()) ); + //it's viewActivated and not viewCreated because the view get his mainwindow only when it is shown. + } + } + else + { + m_image = 0L; + } + + setXMLFile("msnchatui.rc"); + + setMayInvite( true ); +} + +MSNChatSession::~MSNChatSession() +{ + delete m_image; + //force to disconnect the switchboard + //not needed since the m_chatService has us as parent + // if(m_chatService) + // delete m_chatService; + + QMap<unsigned long int, MSNInvitation*>::Iterator it; + for( it = m_invitations.begin(); it != m_invitations.end() ; it = m_invitations.begin()) + { + delete *it; + m_invitations.remove( it ); + } +} + +void MSNChatSession::createChat( const QString &handle, + const QString &address, const QString &auth, const QString &ID ) +{ + /* disabled because i don't want to reopen a chatwindow if we just closed it + * and the contact take much time to type his message + m_newSession= !(ID.isEmpty()); + */ + + if( m_chatService ) + { + kdDebug(14140) << k_funcinfo << "Service already exists, disconnect them." << endl; + delete m_chatService; + } + +// uncomment this line if you don't want to the peer know when you close the window +// setCanBeDeleted( false ); + + m_chatService = new MSNSwitchBoardSocket( static_cast<MSNAccount*>( myself()->account() ) , this); + m_chatService->setUseHttpMethod( static_cast<MSNAccount*>( myself()->account() )->useHttpMethod() ); + m_chatService->setHandle( myself()->account()->accountId() ); + m_chatService->setMsgHandle( handle ); + m_chatService->connectToSwitchBoard( ID, address, auth ); + + connect( m_chatService, SIGNAL( userJoined(const QString&,const QString&,bool)), + this, SLOT( slotUserJoined(const QString&,const QString&,bool) ) ); + connect( m_chatService, SIGNAL( userLeft(const QString&,const QString&)), + this, SLOT( slotUserLeft(const QString&,const QString&) ) ); + connect( m_chatService, SIGNAL( msgReceived( Kopete::Message & ) ), + this, SLOT( slotMessageReceived( Kopete::Message & ) ) ); + connect( m_chatService, SIGNAL( switchBoardClosed() ), + this, SLOT( slotSwitchBoardClosed() ) ); + connect( m_chatService, SIGNAL( receivedTypingMsg( const QString &, bool ) ), + this, SLOT( receivedTypingMsg( const QString &, bool ) ) ); + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if(config->readBoolEntry( "SendTypingNotification" , true) ) + { + connect( this, SIGNAL( myselfTyping( bool ) ), + m_chatService, SLOT( sendTypingMsg( bool ) ) ); + } + connect( m_chatService, SIGNAL( msgAcknowledgement(unsigned int, bool) ), + this, SLOT( slotAcknowledgement(unsigned int, bool) ) ); + connect( m_chatService, SIGNAL( invitation( const QString&, const QString& ) ), + this, SLOT( slotInvitation( const QString&, const QString& ) ) ); + connect( m_chatService, SIGNAL( nudgeReceived(const QString&) ), + this, SLOT( slotNudgeReceived(const QString&) ) ); + connect( m_chatService, SIGNAL( errorMessage(int, const QString& ) ), static_cast<MSNAccount *>(myself()->account()), SLOT( slotErrorMessageReceived(int, const QString& ) ) ); + + if(!m_timeoutTimer) + { + m_timeoutTimer=new QTimer(this); + connect( m_timeoutTimer , SIGNAL(timeout()), this , SLOT(slotConnectionTimeout() ) ); + } + m_timeoutTimer->start(20000,true); +} + +void MSNChatSession::slotUserJoined( const QString &handle, const QString &publicName, bool IRO ) +{ + delete m_timeoutTimer; + m_timeoutTimer=0L; + + if( !account()->contacts()[ handle ] ) + account()->addContact( handle, QString::null, 0L, Kopete::Account::Temporary); + + MSNContact *c = static_cast<MSNContact*>( account()->contacts()[ handle ] ); + + c->setProperty( Kopete::Global::Properties::self()->nickName() , publicName); + + if(c->clientFlags() & MSNProtocol::MSNC4 ) + { + m_actionNudge->setEnabled(true); + } +#if MSN_WEBCAM + if(c->clientFlags() & MSNProtocol::SupportWebcam ) + { + m_actionWebcamReceive->setEnabled(true); + } +#endif + + addContact(c , IRO); // don't show notificaions when we join wesalef + if(!m_messagesQueue.empty() || !m_invitations.isEmpty()) + sendMessageQueue(); + + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if ( members().count()==1 && config->readNumEntry( "DownloadPicture", 1 ) >= 1 && !c->object().isEmpty() && !c->hasProperty(Kopete::Global::Properties::self()->photo().key())) + slotRequestPicture(); +} + +void MSNChatSession::slotUserLeft( const QString &handle, const QString& reason ) +{ + MSNContact *c = static_cast<MSNContact*>( myself()->account()->contacts()[ handle ] ); + if(c) + removeContact(c, reason ); +} + + + +void MSNChatSession::slotSwitchBoardClosed() +{ + //kdDebug(14140) << "MSNChatSession::slotSwitchBoardClosed" << endl; + m_chatService->deleteLater(); + m_chatService=0l; + + cleanMessageQueue( i18n("Connection closed") ); + + if(m_invitations.isEmpty()) + setCanBeDeleted( true ); +} + +void MSNChatSession::slotMessageSent(Kopete::Message &message,Kopete::ChatSession *) +{ + m_newSession=false; + if(m_chatService) + { + int id = m_chatService->sendMsg(message); + if(id == -1) + { + m_messagesQueue.append(message); + kdDebug(14140) << k_funcinfo << "Message added to the queue" <<endl; + } + else if( id== -2 ) //the message has not been sent + { + //FIXME: tell the what window the message has been processed. but we havent't sent it + messageSucceeded(); //that should stop the blonking icon. + } + else if( id == -3) //the message has been sent as an immge + { + appendMessage(message); + messageSucceeded(); + } + else + { + m_messagesSent.insert( id, message ); + message.setBg(QColor()); // clear the bgColor + message.setBody(message.plainBody() , Kopete::Message::PlainText ); //clear every custom tag which are not sent + appendMessage(message); // send the own msg to chat window + } + } + else // There's no switchboard available, so we must create a new one! + { + startChatSession(); + m_messagesQueue.append(message); +// sendMessageQueue(); + //m_msgQueued=new Kopete::Message(message); + } +} + +void MSNChatSession::slotMessageReceived( Kopete::Message &msg ) +{ + m_newSession=false; + if( msg.plainBody().startsWith( "AutoMessage: " ) ) + { + //FIXME: HardCodded color are not so good + msg.setFg( QColor( "SlateGray3" ) ); + QFont f; + f.setItalic( true ); + msg.setFont( f ); + } + appendMessage( msg ); +} + +void MSNChatSession::slotActionInviteAboutToShow() +{ + // We can't simply insert KAction in this menu bebause we don't know when to delete them. + // items inserted with insert items are automatically deleted when we call clear + + m_inviteactions.setAutoDelete(true); + m_inviteactions.clear(); + + m_actionInvite->popupMenu()->clear(); + + + QDictIterator<Kopete::Contact> it( account()->contacts() ); + for( ; it.current(); ++it ) + { + if( !members().contains( it.current() ) && it.current()->isOnline() && it.current() != myself() ) + { + KAction *a=new KopeteContactAction( it.current(), this, + SLOT( slotInviteContact( Kopete::Contact * ) ), m_actionInvite ); + m_actionInvite->insert( a ); + m_inviteactions.append( a ) ; + } + } + KAction *b=new KAction( i18n ("Other..."), 0, this, SLOT( slotInviteOtherContact() ), m_actionInvite, "actionOther" ); + m_actionInvite->insert( b ); + m_inviteactions.append( b ) ; +} + +void MSNChatSession::slotCloseSession() +{ + kdDebug(14140) << k_funcinfo << m_chatService <<endl; + if(m_chatService) + m_chatService->slotCloseSession(); +} + +void MSNChatSession::slotInviteContact( Kopete::Contact *contact ) +{ + if(contact) + inviteContact( contact->contactId() ); +} + +void MSNChatSession::inviteContact(const QString &contactId) +{ + if( m_chatService ) + m_chatService->slotInviteContact( contactId ); + else + startChatSession(); +} + +void MSNChatSession::slotInviteOtherContact() +{ + bool ok; + QString handle = KInputDialog::getText(i18n( "MSN Plugin" ), + i18n( "Please enter the email address of the person you want to invite:" ), + QString::null, &ok ); + if( !ok ) + return; + + if( handle.contains('@') != 1 || handle.contains('.') <1) + { + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n("<qt>You must enter a valid email address.</qt>"), i18n("MSN Plugin")); + return; + } + + inviteContact(handle); +} + + +void MSNChatSession::sendMessageQueue() +{ + if(!m_chatService) + { + kdDebug(14140) <<k_funcinfo << "Service doesn't exist" <<endl; + return; + } +// kdDebug(14140) << "MSNChatSession::sendMessageQueue: " << m_messagesQueue.count() <<endl; + for ( QValueList<Kopete::Message>::iterator it = m_messagesQueue.begin(); it!=m_messagesQueue.end(); it = m_messagesQueue.begin() ) + { + //m_chatService->sendMsg( *it) ; + slotMessageSent(*it , this); + m_messagesQueue.remove(it); + } + + + QMap<unsigned long int, MSNInvitation*>::Iterator it; + for( it = m_invitations.begin(); it != m_invitations.end() ; ++it) + { + if(! (*it)->incoming() && (*it)->state()<MSNInvitation::Invited) + { + m_chatService->sendCommand( "MSG" , "N", true, (*it)->invitationHead().utf8() ); + (*it)->setState(MSNInvitation::Invited); + } + } +} + +void MSNChatSession::slotAcknowledgement(unsigned int id, bool ack) +{ + if ( !m_messagesSent.contains( id ) ) + { + // This is maybe a ACK/NAK for a non-messaging message + return; + } + + if ( !ack ) + { + Kopete::Message m = m_messagesSent[ id ]; + QString body = i18n( "The following message has not been sent correctly:\n%1" ).arg( m.plainBody() ); + Kopete::Message msg = Kopete::Message( m.to().first(), members(), body, Kopete::Message::Internal, Kopete::Message::PlainText ); + appendMessage( msg ); + //stop the stupid animation + messageSucceeded(); + } + else + { + messageSucceeded(); + } + + m_messagesSent.remove( id ); +} + +void MSNChatSession::slotInvitation(const QString &handle, const QString &msg) +{ + //FIXME! a contact from another account can send a file + MSNContact *c = static_cast<MSNContact*>( myself()->account()->contacts()[ handle ] ); + if(!c) + return; + + QRegExp rx("Invitation-Cookie: ([0-9]*)"); + rx.search(msg); + long unsigned int cookie=rx.cap(1).toUInt(); + + if(m_invitations.contains(cookie)) + { + MSNInvitation *msnI=m_invitations[cookie]; + msnI->parseInvitation(msg); + } + else if( msg.contains("Invitation-Command: INVITE") ) + { + if( msg.contains(MSNFileTransferSocket::applicationID()) ) + { + MSNFileTransferSocket *MFTS=new MSNFileTransferSocket(myself()->account()->accountId(),c,true,this); + connect(MFTS, SIGNAL( done(MSNInvitation*) ) , this , SLOT( invitationDone(MSNInvitation*) )); + m_invitations.insert( cookie , MFTS); + MFTS->parseInvitation(msg); + setCanBeDeleted(false); + } + else + { + MSNInvitation *i=0l; + emit invitation( i , msg, cookie, this, c ); + if(i) + { + m_invitations.insert( cookie , i ); + //don't delete this if all invitation are not done + setCanBeDeleted(false); + } + else + { + rx=QRegExp("Application-Name: ([^\\r\\n]*)"); + rx.search(msg); + QString inviteName = rx.cap( 1 ); + + QString body = i18n( + "%1 has sent an unimplemented invitation, the invitation was rejected.\n" + "The invitation was: %2" ) + .arg( c->property( Kopete::Global::Properties::self()->nickName()).value().toString(), inviteName ); + Kopete::Message tmpMsg = Kopete::Message( c , members() , body , Kopete::Message::Internal, Kopete::Message::PlainText); + appendMessage(tmpMsg); + + m_chatService->sendCommand( "MSG" , "N", true, MSNInvitation::unimplemented(cookie) ); + } + } + } +} + +void MSNChatSession::invitationDone(MSNInvitation* MFTS) +{ + kdDebug(14140) << k_funcinfo <<endl; + m_invitations.remove(MFTS->cookie()); +// MFTS->deleteLater(); + delete MFTS; + if(!m_chatService && m_invitations.isEmpty()) + setCanBeDeleted(true); +} + +void MSNChatSession::sendFile(const QString &fileLocation, const QString &/*fileName*/, + long unsigned int fileSize) +{ + // TODO create a switchboard to send the file is one is not available. + if(m_chatService && members().getFirst()) + { + m_chatService->PeerDispatcher()->sendFile(fileLocation, (Q_INT64)fileSize, members().getFirst()->contactId()); + } +} + +void MSNChatSession::initInvitation(MSNInvitation* invitation) +{ + connect(invitation->object(), SIGNAL( done(MSNInvitation*) ) , this , SLOT( invitationDone(MSNInvitation*) )); + m_invitations.insert( invitation->cookie() , invitation); + + if(m_chatService) + { + m_chatService->sendCommand( "MSG" , "N", true, invitation->invitationHead().utf8() ); + invitation->setState(MSNInvitation::Invited); + } + else + { + startChatSession(); + } +} + +void MSNChatSession::slotRequestPicture() +{ + QPtrList<Kopete::Contact> mb=members(); + MSNContact *c = static_cast<MSNContact*>( mb.first() ); + if(!c) + return; + + if( !c->hasProperty(Kopete::Global::Properties::self()->photo().key())) + { + if(m_chatService) + { + if( !c->object().isEmpty() ) + m_chatService->requestDisplayPicture(); + } + else if(myself()->onlineStatus().isDefinitelyOnline() && myself()->onlineStatus().status() != Kopete::OnlineStatus::Invisible ) + startChatSession(); + } + else + { //we already have the picture, just show it. + KRun::runURL( KURL::fromPathOrURL( c->property(Kopete::Global::Properties::self()->photo()).value().toString() ), "image/png" ); + } + +} + +void MSNChatSession::slotDisplayPictureChanged() +{ + QPtrList<Kopete::Contact> mb=members(); + MSNContact *c = static_cast<MSNContact *>( mb.first() ); + if ( c && m_image ) + { + if(c->hasProperty(Kopete::Global::Properties::self()->photo().key())) + { + int sz=22; + // get the size of the toolbar were the aciton is plugged. + // if you know a better way to get the toolbar, let me know + KMainWindow *w= view(false) ? dynamic_cast<KMainWindow*>( view(false)->mainWidget()->topLevelWidget() ) : 0L; + if(w) + { + //We connected that in the constructor. we don't need to keep this slot active. + disconnect( Kopete::ChatSessionManager::self() , SIGNAL(viewActivated(KopeteView* )) , this, SLOT(slotDisplayPictureChanged()) ); + + QPtrListIterator<KToolBar> it=w->toolBarIterator() ; + KAction *imgAction=actionCollection()->action("msnDisplayPicture"); + if(imgAction) while(it) + { + KToolBar *tb=*it; + if(imgAction->isPlugged(tb)) + { + sz=tb->iconSize(); + //ipdate if the size of the toolbar change. + disconnect(tb, SIGNAL(modechange()), this, SLOT(slotDisplayPictureChanged())); + connect(tb, SIGNAL(modechange()), this, SLOT(slotDisplayPictureChanged())); + break; + } + ++it; + } + } + QString imgURL=c->property(Kopete::Global::Properties::self()->photo()).value().toString(); + QImage scaledImg = QPixmap( imgURL ).convertToImage().smoothScale( sz, sz ); + if(!scaledImg.isNull()) + m_image->setPixmap( scaledImg ); + else + { //the image has maybe not been transfered correctly.. force to download again + c->removeProperty(Kopete::Global::Properties::self()->photo()); + //slotDisplayPictureChanged(); //don't do that or we might end in a infinite loop + } + QToolTip::add( m_image, "<qt><img src=\"" + imgURL + "\"></qt>" ); + + } + else + { + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if ( config->readNumEntry( "DownloadPicture", 1 ) >= 1 && !c->object().isEmpty() ) + slotRequestPicture(); + } + } +} + +void MSNChatSession::slotDebugRawCommand() +{ +#if !defined NDEBUG + if ( !m_chatService ) + return; + + MSNDebugRawCmdDlg *dlg = new MSNDebugRawCmdDlg( 0L ); + int result = dlg->exec(); + if( result == QDialog::Accepted && m_chatService ) + { + m_chatService->sendCommand( dlg->command(), dlg->params(), + dlg->addId(), dlg->msg().replace("\n","\r\n").utf8() ); + } + delete dlg; +#endif +} + + +void MSNChatSession::receivedTypingMsg( const QString &contactId, bool b ) +{ + MSNContact *c = dynamic_cast<MSNContact *>( account()->contacts()[ contactId ] ); + if(c && m_newSession && !view(false)) + { + //this was originaly in MSNAccount::slotCreateChat + KGlobal::config()->setGroup( "MSN" ); + bool notifyNewChat = KGlobal::config()->readBoolEntry( "NotifyNewChat", false ); + if ( notifyNewChat ) + { + // this internal message should open the window if they not exist + QString body = i18n( "%1 has started a chat with you" ).arg( c->metaContact()->displayName() ); + Kopete::Message tmpMsg = Kopete::Message( c, members(), body, Kopete::Message::Internal, Kopete::Message::PlainText ); + appendMessage( tmpMsg ); + } + } + m_newSession=false; + if(c) + Kopete::ChatSession::receivedTypingMsg(c,b); +} + +void MSNChatSession::slotSendNudge() +{ + if(m_chatService) + { + m_chatService->sendNudge(); + Kopete::Message msg = Kopete::Message( myself(), members() , i18n ( "has sent a nudge" ), Kopete::Message::Outbound, + Kopete::Message::PlainText, QString(), Kopete::Message::TypeAction ); + appendMessage( msg ); + + } +} + + +void MSNChatSession::slotNudgeReceived(const QString& handle) +{ + Kopete::Contact *c = account()->contacts()[ handle ] ; + if(!c) + c=members().getFirst(); + Kopete::Message msg = Kopete::Message(c, myself(), i18n ( "has sent you a nudge" ), Kopete::Message::Inbound, + Kopete::Message::PlainText, QString(), Kopete::Message::TypeAction ); + appendMessage( msg ); + // Emit the nudge/buzz notification (configured by user). + emitNudgeNotification(); +} + + +void MSNChatSession::slotWebcamReceive() +{ +#if MSN_WEBCAM + if(m_chatService && members().getFirst()) + { + m_chatService->PeerDispatcher()->startWebcam(myself()->contactId() , members().getFirst()->contactId() , true); + } +#endif +} + +void MSNChatSession::slotWebcamSend() +{ +#if MSN_WEBCAM + kdDebug(14140) << k_funcinfo << endl; + if(m_chatService && members().getFirst()) + { + m_chatService->PeerDispatcher()->startWebcam(myself()->contactId() , members().getFirst()->contactId() , false); + } +#endif +} + + +void MSNChatSession::slotSendFile() + { + QPtrList<Kopete::Contact>contacts = members(); + static_cast<MSNContact *>(contacts.first())->sendFile(); + } + +void MSNChatSession::startChatSession() +{ + QPtrList<Kopete::Contact> mb=members(); + static_cast<MSNAccount*>( account() )->slotStartChatSession( mb.first()->contactId() ); + + if(!m_timeoutTimer) + { + m_timeoutTimer=new QTimer(this); + connect( m_timeoutTimer , SIGNAL(timeout()), this , SLOT(slotConnectionTimeout() ) ); + } + m_timeoutTimer->start(20000, true); +} + + +void MSNChatSession::cleanMessageQueue( const QString & reason ) +{ + delete m_timeoutTimer; + m_timeoutTimer=0L; + + uint nb=m_messagesQueue.count()+m_messagesSent.count(); + if(nb==0) + return; + else if(nb==1) + { + Kopete::Message m; + if(m_messagesQueue.count() == 1) + m=m_messagesQueue.first(); + else + m=m_messagesSent.begin().data(); + + QString body=i18n("The following message has not been sent correctly (%1): \n%2").arg(reason, m.plainBody()); + Kopete::Message msg = Kopete::Message(m.to().first() , members() , body , Kopete::Message::Internal, Kopete::Message::PlainText); + appendMessage(msg); + } + else + { + Kopete::Message m; + QString body=i18n("These messages have not been sent correctly (%1): <br /><ul>").arg(reason); + for ( QMap<unsigned int , Kopete::Message>::iterator it = m_messagesSent.begin(); it!=m_messagesSent.end(); it = m_messagesSent.begin() ) + { + m=it.data(); + body+= "<li>"+m.escapedBody()+"</li>"; + m_messagesSent.remove(it); + } + for ( QValueList<Kopete::Message>::iterator it = m_messagesQueue.begin(); it!=m_messagesQueue.end(); it = m_messagesQueue.begin() ) + { + m=(*it); + body+= "<li>"+m.escapedBody()+"</li>"; + m_messagesQueue.remove(it); + } + body+="</ul>"; + Kopete::Message msg = Kopete::Message(m.to().first() , members() , body , Kopete::Message::Internal, Kopete::Message::RichText); + appendMessage(msg); + + } + m_messagesQueue.clear(); + m_messagesSent.clear(); + messageSucceeded(); //stop stupid animation +} + +void MSNChatSession::slotConnectionTimeout() +{ + m_connectionTry++; + if(m_chatService) + { + disconnect(m_chatService , 0 , this , 0 ); + m_chatService->deleteLater(); + m_chatService=0L; + } + + if( m_connectionTry > 3 ) + { + cleanMessageQueue( i18n("Impossible to establish the connection") ); + delete m_timeoutTimer; + m_timeoutTimer=0L; + return; + } + startChatSession(); + +} + + + + +#include "msnchatsession.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnchatsession.h b/kopete/protocols/msn/msnchatsession.h new file mode 100644 index 00000000..8011574e --- /dev/null +++ b/kopete/protocols/msn/msnchatsession.h @@ -0,0 +1,140 @@ +/* + msnchatsession.h - MSN Message Manager + + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNMESSAGEMANAGER_H +#define MSNMESSAGEMANAGER_H + +#include "kopetechatsession.h" + +class MSNSwitchBoardSocket; +class KActionCollection; +class MSNInvitation; +class MSNContact; +class KActionMenu; +class QLabel; + + +/** + * @author Olivier Goffart + */ +class KOPETE_EXPORT MSNChatSession : public Kopete::ChatSession +{ + Q_OBJECT + +public: + MSNChatSession( Kopete::Protocol *protocol, const Kopete::Contact *user, Kopete::ContactPtrList others, const char *name = 0 ); + ~MSNChatSession(); + + void createChat( const QString &handle, const QString &address, const QString &auth, const QString &ID = QString::null ); + + MSNSwitchBoardSocket *service() { return m_chatService; }; + + void sendFile( const QString &fileLocation, const QString &fileName, + long unsigned int fileSize ); + + /** + * append an invitation in the invitation map, and send the first invitation message + */ + void initInvitation(MSNInvitation* invitation); + + virtual void inviteContact(const QString& ); + +public slots: + void slotCloseSession(); + void slotInviteOtherContact(); + + void invitationDone( MSNInvitation* ); + + void slotRequestPicture(); + + /** + * this is a reimplementation of ChatSesstion slot. + * the original slot is not virtual, but that's not a problem because it's a slot. + */ + virtual void receivedTypingMsg( const QString &, bool ); + + void slotConnectionTimeout(); + +private slots: + void slotMessageSent( Kopete::Message &message, Kopete::ChatSession *kmm ); + void slotMessageReceived( Kopete::Message &message ); + + void slotUserJoined( const QString &handle, const QString &publicName, bool IRO ); + void slotUserLeft( const QString &handle, const QString &reason ); + void slotSwitchBoardClosed(); + void slotInviteContact( Kopete::Contact *contact ); + void slotAcknowledgement( unsigned int id, bool ack ); + void slotInvitation( const QString &handle, const QString &msg ); + + void slotActionInviteAboutToShow(); + + void slotDisplayPictureChanged(); + + /** + * (debug) + */ + void slotDebugRawCommand(); + + void slotSendNudge(); + void slotWebcamReceive(); + void slotWebcamSend(); + void slotSendFile(); + + void slotNudgeReceived(const QString& handle); + +private: + + MSNSwitchBoardSocket *m_chatService; + QString otherString; + KActionMenu *m_actionInvite; + QPtrList<KAction> m_inviteactions; + KAction *m_actionNudge; + KAction *m_actionWebcamReceive; + KAction *m_actionWebcamSend; + + //Messages sent before the ending of the connection are queued + QValueList<Kopete::Message> m_messagesQueue; + void sendMessageQueue(); + void cleanMessageQueue( const QString &reason); + void startChatSession(); + + QMap<unsigned int, Kopete::Message> m_messagesSent; + + QMap<long unsigned int, MSNInvitation*> m_invitations; + + + /** + * weither or not the "has opened a new chat" message need to be sent if the user is typing + */ + bool m_newSession; + + QLabel *m_image; + QTimer *m_timeoutTimer; + uint m_connectionTry; + + +signals: + /* + * This signal is relayed to the protocol and after, to plugins + */ + void invitation(MSNInvitation*& invitation, const QString &bodyMSG , long unsigned int cookie , MSNChatSession* msnMM , MSNContact* c ); +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/msn/msnchatui.rc b/kopete/protocols/msn/msnchatui.rc new file mode 100644 index 00000000..6dbc94ca --- /dev/null +++ b/kopete/protocols/msn/msnchatui.rc @@ -0,0 +1,27 @@ +<!DOCTYPE kpartgui> +<kpartgui version="12" name="kopete_msn_chat"> + <MenuBar> + <Menu noMerge="1" name="file"> + <text>&Chat</text> + <Action name="msnWebcamReceive" /> + <Action name="msnWebcamSend" /> + <Action name="msnSendFile" /> + <Action name="msnSendNudge" /> + <Action name="msnRequestDisplayPicture" /> + <Action name="msnInvite" /> +<!-- <Menu name="debug"> + <text>&Debug</text> + <Action name="msnDebugRawCommand" /> + </Menu> --> + </Menu> + </MenuBar> + + + <ToolBar name="statusToolBar"> + <Action name="msnDisplayPicture" /> + <Action name="msnWebcamReceive" /> + <Action name="msnWebcamSend" /> + <Action name="msnSendFile" /> + <Action name="msnSendNudge" /> + </ToolBar> +</kpartgui> diff --git a/kopete/protocols/msn/msncontact.cpp b/kopete/protocols/msn/msncontact.cpp new file mode 100644 index 00000000..8a490a1b --- /dev/null +++ b/kopete/protocols/msn/msncontact.cpp @@ -0,0 +1,713 @@ +/* + msncontact.cpp - MSN Contact + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Ryan Cumming <bodnar42@phalynx.dhs.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Lesser General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + ************************************************************************* +*/ + +#include "msncontact.h" + +#include <qcheckbox.h> + +#undef KDE_NO_COMPAT +#include <kaction.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <klineedit.h> +#include <klocale.h> +#include <kstandarddirs.h> +#include <kmessagebox.h> +#include <krun.h> +#include <ktempfile.h> +#include <kconfig.h> +#include <kglobal.h> +#include <qregexp.h> +#include <kio/job.h> + +#include "kopetecontactlist.h" +#include "kopetechatsessionmanager.h" +#include "kopetemetacontact.h" +#include "kopetegroup.h" +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" + +#include "msninfo.h" +#include "msnchatsession.h" +#include "msnnotifysocket.h" +#include "msnaccount.h" + +MSNContact::MSNContact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent ) +: Kopete::Contact( account, id, parent ) +{ + m_deleted = false; + m_allowed = false; + m_blocked = false; + m_reversed = false; + m_moving = false; + + m_clientFlags=0; + + setFileCapable( true ); + + // When we are not connected, it's because we are loading the contactlist. + // so we set the initial status to offline. + // We set offline directly because modifying the status after is too slow. + // (notification, contactlist updating,....) + // + // FIXME: Hacks like these shouldn't happen in the protocols, but should be + // covered properly at the libkopete level instead - Martijn + // + // When we are connected, it can be because the user added a contact with the + // wizard, and it can be because we are creating a temporary contact. + // if it's added by the wizard, the status will be set immediately after. + // if it's a temporary contact, better to set the unknown status. + setOnlineStatus( ( parent && parent->isTemporary() ) ? MSNProtocol::protocol()->UNK : MSNProtocol::protocol()->FLN ); + + actionBlock = 0L; + + setProperty(MSNProtocol::protocol()->propEmail, id); +} + +MSNContact::~MSNContact() +{ + kdDebug(14140) << k_funcinfo << endl; +} + +bool MSNContact::isReachable() +{ + if ( account()->isConnected() && isOnline() && account()->myself()->onlineStatus() != MSNProtocol::protocol()->HDN ) + return true; + + MSNChatSession *kmm=dynamic_cast<MSNChatSession*>(manager(Kopete::Contact::CannotCreate)); + if( kmm && kmm->service() ) //the chat socket is open. than mean message will be sent + return true; + + // When we are invisible we can't start a chat with others, make isReachable return false + // (This is an MSN limitation, not a problem in Kopete) + if ( !account()->isConnected() || account()->myself()->onlineStatus() == MSNProtocol::protocol()->HDN ) + return false; + + //if the contact is offline, it is impossible to send it a message. but it is impossible + //to be sure the contact is realy offline. For example, if the contact is not on the contactlist for + //some reason. + if( onlineStatus() == MSNProtocol::protocol()->FLN && ( isAllowed() || isBlocked() ) && !serverGroups().isEmpty() ) + return false; + + return true; +} + +Kopete::ChatSession *MSNContact::manager( Kopete::Contact::CanCreateFlags canCreate ) +{ + Kopete::ContactPtrList chatmembers; + chatmembers.append(this); + + Kopete::ChatSession *_manager = Kopete::ChatSessionManager::self()->findChatSession( account()->myself(), chatmembers, protocol() ); + MSNChatSession *manager = dynamic_cast<MSNChatSession*>( _manager ); + if(!manager && canCreate==Kopete::Contact::CanCreate) + { + manager = new MSNChatSession( protocol(), account()->myself(), chatmembers ); + static_cast<MSNAccount*>( account() )->slotStartChatSession( contactId() ); + } + return manager; +} + +QPtrList<KAction> *MSNContact::customContextMenuActions() +{ + QPtrList<KAction> *m_actionCollection = new QPtrList<KAction>; + + // Block/unblock Contact + QString label = isBlocked() ? i18n( "Unblock User" ) : i18n( "Block User" ); + if( !actionBlock ) + { + actionBlock = new KAction( label, "msn_blocked",0, this, SLOT( slotBlockUser() ), + this, "actionBlock" ); + + //show profile + actionShowProfile = new KAction( i18n("Show Profile") , 0, this, SLOT( slotShowProfile() ), + this, "actionShowProfile" ); + + // Send mail (only available if it is an hotmail account) + actionSendMail = new KAction( i18n("Send Email...") , "mail_generic",0, this, SLOT( slotSendMail() ), + this, "actionSendMail" ); + + // Invite to receive webcam + actionWebcamReceive = new KAction( i18n( "View Contact's Webcam" ), "webcamreceive", 0, this, SLOT(slotWebcamReceive() ), this, "msnWebcamReceive" ) ; + + //Send webcam action + actionWebcamSend = new KAction( i18n( "Send Webcam" ), "webcamsend", 0, this, SLOT(slotWebcamSend() ), this, "msnWebcamSend" ) ; + } + else + actionBlock->setText( label ); + + actionSendMail->setEnabled( static_cast<MSNAccount*>(account())->isHotmail()); + + m_actionCollection->append( actionBlock ); + m_actionCollection->append( actionShowProfile ); + m_actionCollection->append( actionSendMail ); + m_actionCollection->append( actionWebcamReceive ); + m_actionCollection->append( actionWebcamSend ); + + + return m_actionCollection; +} + +void MSNContact::slotBlockUser() +{ + MSNNotifySocket *notify = static_cast<MSNAccount*>( account() )->notifySocket(); + if( !notify ) + { + KMessageBox::error( Kopete::UI::Global::mainWidget(), + i18n( "<qt>Please go online to block or unblock a contact.</qt>" ), + i18n( "MSN Plugin" )); + return; + } + + if( m_blocked ) + { + notify->removeContact( contactId(), MSNProtocol::BL, QString::null, QString::null ); + } + else + { + if(m_allowed) + notify->removeContact( contactId(), MSNProtocol::AL, QString::null, QString::null ); + else + notify->addContact( contactId(), MSNProtocol::BL, QString::null, QString::null, QString::null ); + } +} + +void MSNContact::slotUserInfo() +{ + KDialogBase *infoDialog=new KDialogBase( 0l, "infoDialog", /*modal = */false, QString::null, KDialogBase::Close , KDialogBase::Close, false ); + QString nick=property( Kopete::Global::Properties::self()->nickName()).value().toString(); + QString personalMessage=property( MSNProtocol::protocol()->propPersonalMessage).value().toString(); + MSNInfo *info=new MSNInfo ( infoDialog,"info"); + info->m_id->setText( contactId() ); + info->m_displayName->setText(nick); + info->m_personalMessage->setText(personalMessage); + info->m_phh->setText(m_phoneHome); + info->m_phw->setText(m_phoneWork); + info->m_phm->setText(m_phoneMobile); + info->m_reversed->setChecked(m_reversed); + + connect( info->m_reversed, SIGNAL(toggled(bool)) , this, SLOT(slotUserInfoDialogReversedToggled())); + + infoDialog->setMainWidget(info); + infoDialog->setCaption(nick); + infoDialog->show(); +} + +void MSNContact::slotUserInfoDialogReversedToggled() +{ + //workaround to make this checkboxe readonly + const QCheckBox *cb=dynamic_cast<const QCheckBox*>(sender()); + if(cb && cb->isChecked()!=m_reversed) + const_cast<QCheckBox*>(cb)->setChecked(m_reversed); +} + +void MSNContact::deleteContact() +{ + kdDebug( 14140 ) << k_funcinfo << endl; + + MSNNotifySocket *notify = static_cast<MSNAccount*>( account() )->notifySocket(); + if( notify ) + { + if( hasProperty(MSNProtocol::protocol()->propGuid.key()) ) + { + // Remove from all groups he belongs (if applicable) + for( QMap<QString, Kopete::Group*>::Iterator it = m_serverGroups.begin(); it != m_serverGroups.end(); ++it ) + { + kdDebug(14140) << k_funcinfo << "Removing contact from group \"" << it.key() << "\"" << endl; + notify->removeContact( contactId(), MSNProtocol::FL, guid(), it.key() ); + } + + // Then trully remove it from server contact list, + // because only removing the contact from his groups isn't sufficent from MSNP11. + kdDebug( 14140 ) << k_funcinfo << "Removing contact from top-level." << endl; + notify->removeContact( contactId(), MSNProtocol::FL, guid(), QString::null); + } + else + { + kdDebug( 14140 ) << k_funcinfo << "The contact is already removed from server, just delete it" << endl; + deleteLater(); + } + } + else + { + // FIXME: This case should be handled by Kopete, not by the plugins :( - Martijn + // FIXME: We should be able to delete contacts offline, and remove it from server next time we go online - Olivier + KMessageBox::error( Kopete::UI::Global::mainWidget(), i18n( "<qt>Please go online to remove a contact from your contact list.</qt>" ), i18n( "MSN Plugin" )); + } +} + +bool MSNContact::isBlocked() const +{ + return m_blocked; +} + +void MSNContact::setBlocked( bool blocked ) +{ + if( m_blocked != blocked ) + { + m_blocked = blocked; + //update the status + setOnlineStatus(m_currentStatus); + //m_currentStatus is used here. previously it was onlineStatus() but this may cause problem when + // the account is offline because of the Kopete::Contact::OnlineStatus() account offline hack. + } +} + +bool MSNContact::isAllowed() const +{ + return m_allowed; +} + +void MSNContact::setAllowed( bool allowed ) +{ + m_allowed = allowed; +} + +bool MSNContact::isReversed() const +{ + return m_reversed; +} + +void MSNContact::setReversed( bool reversed ) +{ + m_reversed= reversed; +} + +bool MSNContact::isDeleted() const +{ + return m_deleted; +} + +void MSNContact::setDeleted( bool deleted ) +{ + m_deleted= deleted; +} + +uint MSNContact::clientFlags() const +{ + return m_clientFlags; +} + +void MSNContact::setClientFlags( uint flags ) +{ + if(m_clientFlags != flags) + { + if(hasProperty( MSNProtocol::protocol()->propClient.key() )) + { + if( flags & MSNProtocol::WebMessenger) + setProperty( MSNProtocol::protocol()->propClient , i18n("Web Messenger") ); + else if( flags & MSNProtocol::WindowsMobile) + setProperty( MSNProtocol::protocol()->propClient , i18n("Windows Mobile") ); + else if( flags & MSNProtocol::MSNMobileDevice) + setProperty( MSNProtocol::protocol()->propClient , i18n("MSN Mobile") ); + else if( m_obj.contains("kopete") ) + setProperty( MSNProtocol::protocol()->propClient , i18n("Kopete") ); + } + + } + m_clientFlags=flags; +} + +void MSNContact::setInfo(const QString &type,const QString &data ) +{ + if( type == "PHH" ) + { + m_phoneHome = data; + setProperty(MSNProtocol::protocol()->propPhoneHome, data); + } + else if( type == "PHW" ) + { + m_phoneWork=data; + setProperty(MSNProtocol::protocol()->propPhoneWork, data); + } + else if( type == "PHM" ) + { + m_phoneMobile = data; + setProperty(MSNProtocol::protocol()->propPhoneMobile, data); + } + else if( type == "MOB" ) + { + if( data == "Y" ) + m_phone_mob = true; + else if( data == "N" ) + m_phone_mob = false; + else + kdDebug( 14140 ) << k_funcinfo << "Unknown MOB " << data << endl; + } + else if( type == "MFN" ) + { + setProperty(Kopete::Global::Properties::self()->nickName(), data ); + } + else + { + kdDebug( 14140 ) << k_funcinfo << "Unknow info " << type << " " << data << endl; + } +} + + +void MSNContact::serialize( QMap<QString, QString> &serializedData, QMap<QString, QString> & /* addressBookData */ ) +{ + // Contact id and display name are already set for us, only add the rest + QString groups; + bool firstEntry = true; + for( QMap<QString, Kopete::Group *>::ConstIterator it = m_serverGroups.begin(); it != m_serverGroups.end(); ++it ) + { + if( !firstEntry ) + { + groups += ","; + firstEntry = true; + } + groups += it.key(); + } + + QString lists="C"; + if(m_blocked) + lists +="B"; + if(m_allowed) + lists +="A"; + if(m_reversed) + lists +="R"; + + serializedData[ "groups" ] = groups; + serializedData[ "PHH" ] = m_phoneHome; + serializedData[ "PHW" ] = m_phoneWork; + serializedData[ "PHM" ] = m_phoneMobile; + serializedData[ "lists" ] = lists; + serializedData[ "obj" ] = m_obj; + serializedData[ "contactGuid" ] = guid(); +} + + +QString MSNContact::guid(){ return property(MSNProtocol::protocol()->propGuid).value().toString(); } + +QString MSNContact::phoneHome(){ return m_phoneHome ;} +QString MSNContact::phoneWork(){ return m_phoneWork ;} +QString MSNContact::phoneMobile(){ return m_phoneMobile ;} + + +const QMap<QString, Kopete::Group*> MSNContact::serverGroups() const +{ + return m_serverGroups; +} +void MSNContact::clearServerGroups() +{ + m_serverGroups.clear(); +} + + +void MSNContact::sync( unsigned int changed ) +{ + if( ! (changed & Kopete::Contact::MovedBetweenGroup) ) + return; //we are only interested by a change in groups + + if(!metaContact() || metaContact()->isTemporary() ) + return; + + if(m_moving) + { + //We need to make sure that syncGroups is not called twice successively + // because m_serverGroups will be only updated with the reply of the server + // and then, the same command can be sent twice. + // FIXME: if this method is called a seconds times, that mean change can be + // done in the contactlist. we should found a way to recall this + // method later. (a QTimer?) + kdDebug( 14140 ) << k_funcinfo << " This contact is already moving. Abort sync id: " << contactId() << endl; + return; + } + + MSNNotifySocket *notify = static_cast<MSNAccount*>( account() )->notifySocket(); + if( !notify ) + { + //We are not connected, we will doing it next connection. + //Force to reload the whole contactlist from server to suync groups when connecting + account()->configGroup()->writeEntry("serial", 0 ); + return; + } + + if(m_deleted) //the contact hasn't been synced from server yet. + return; + + unsigned int count=m_serverGroups.count(); + + //Don't add the contact if it's myself. + if(count==0 && contactId() == account()->accountId()) + return; + + //STEP ONE : add the contact to every kopetegroups where the MC is + QPtrList<Kopete::Group> groupList = metaContact()->groups(); + for ( Kopete::Group *group = groupList.first(); group; group = groupList.next() ) + { + //For each group, ensure it is on the MSN server + if( !group->pluginData( protocol() , account()->accountId() + " id" ).isEmpty() ) + { + QString Gid=group->pluginData( protocol(), account()->accountId() + " id" ); + if( !static_cast<MSNAccount*>( account() )->m_groupList.contains(Gid) ) + { // ohoh! something is corrupted on the contactlist.xml + // anyway, we never should add a contact to an unexisting group on the server. + // This shouln't be possible anymore 2004-06-10 -Olivier + + //repair the problem + group->setPluginData( protocol() , account()->accountId() + " id" , QString::null); + group->setPluginData( protocol() , account()->accountId() + " displayName" , QString::null); + kdWarning( 14140 ) << k_funcinfo << " Group " << group->displayName() << " marked with id #" <<Gid << " does not seems to be anymore on the server" << endl; + + if(!group->displayName().isEmpty() && group->type() == Kopete::Group::Normal) //not the top-level + { + //Create the group and add the contact + static_cast<MSNAccount*>( account() )->addGroup( group->displayName(),contactId() ); + count++; + m_moving=true; + } + } + else if( !m_serverGroups.contains(Gid) ) + { + //Add the contact to the group on the server + notify->addContact( contactId(), MSNProtocol::FL, QString::null, guid(), Gid ); + count++; + m_moving=true; + } + } + else + { + if(!group->displayName().isEmpty() && group->type() == Kopete::Group::Normal) //not the top-level + { + //Create the group and add the contact + static_cast<MSNAccount*>( account() )->addGroup( group->displayName(),contactId() ); + + //WARNING: if contact is not correctly added (because the group was not aded corrdctly for hinstance), + // if we increment the count, the contact can be deleted from the old group, and be lost :-( + count++; + m_moving=true; + } + } + } + + //STEP TWO : remove the contact from groups where the MC is not, but let it at least in one group + + //contact is not in that group. on the server. we will remove them dirrectly after the loop + QValueList<QString> removinglist; + + for( QMap<QString, Kopete::Group*>::Iterator it = m_serverGroups.begin();(count > 1 && it != m_serverGroups.end()); ++it ) + { + if( !static_cast<MSNAccount*>( account() )->m_groupList.contains(it.key()) ) + { // ohoh! something is corrupted on the contactlist.xml + // anyway, we never should add a contact to an unexisting group on the server. + + //repair the problem ... //contactRemovedFromGroup( it.key() ); + // ... later (we can't remove it from the map now ) + removinglist.append(it.key()); + count--; + + kdDebug( 14140 ) << k_funcinfo << "the group marked with id #" << it.key() << " does not seems to be anymore on the server" << endl; + + continue; + } + + Kopete::Group *group=it.data(); + if(!group) //we can't trust the data of it() see in MSNProtocol::deserializeContact why + group=static_cast<MSNAccount*>( account() )->m_groupList[it.key()]; + if( !metaContact()->groups().contains(group) ) + { + m_moving=true; + notify->removeContact( contactId(), MSNProtocol::FL, guid(), it.key() ); + count--; + } + } + + for(QValueList<QString>::Iterator it= removinglist.begin() ; it != removinglist.end() ; ++it ) + contactRemovedFromGroup(*it); + + //FINAL TEST: is the contact at least in a group.. + // this may happens if we just added a temporary contact to top-level + // we add the contact to the group #0 (the default one) + /*if(count==0) + { +// notify->addContact( contactId(), MSNProtocol::FL, QString::null, guid(), "0"); + }*/ +} + +void MSNContact::contactAddedToGroup( const QString& groupId, Kopete::Group *group ) +{ + m_serverGroups.insert( groupId, group ); + m_moving=false; +} + +void MSNContact::contactRemovedFromGroup( const QString& groupId ) +{ + m_serverGroups.remove( groupId ); + if(m_serverGroups.isEmpty() && !m_moving) + { + deleteLater(); + } + m_moving=false; +} + + +void MSNContact::rename( const QString &newName ) +{ + //kdDebug( 14140 ) << k_funcinfo << "From: " << displayName() << ", to: " << newName << endl; + +/* if( newName == displayName() ) + return;*/ + + // FIXME: This should be called anymore. + MSNNotifySocket *notify = static_cast<MSNAccount*>( account() )->notifySocket(); + if( notify ) + { + notify->changePublicName( newName, contactId() ); + } +} + +void MSNContact::slotShowProfile() +{ + KRun::runURL( KURL( QString::fromLatin1("http://members.msn.com/?pgmarket=it-it&mem=") + contactId()) , "text/html" ); +} + + +/** + * FIXME: Make this a standard KMM API call + */ +void MSNContact::sendFile( const KURL &sourceURL, const QString &altFileName, uint /*fileSize*/ ) +{ + QString filePath; + + //If the file location is null, then get it from a file open dialog + if( !sourceURL.isValid() ) + filePath = KFileDialog::getOpenFileName( QString::null ,"*", 0l , i18n( "Kopete File Transfer" )); + else + filePath = sourceURL.path(-1); + + //kdDebug(14140) << "MSNContact::sendFile: File chosen to send:" << fileName << endl; + + if ( !filePath.isEmpty() ) + { + Q_UINT32 fileSize = QFileInfo(filePath).size(); + //Send the file + static_cast<MSNChatSession*>( manager(Kopete::Contact::CanCreate) )->sendFile( filePath, altFileName, fileSize ); + + } +} + +void MSNContact::setOnlineStatus(const Kopete::OnlineStatus& status) +{ + if(isBlocked() && status.internalStatus() < 15) + { + Kopete::Contact::setOnlineStatus( + Kopete::OnlineStatus(status.status() , + (status.weight()==0) ? 0 : (status.weight() -1) , + protocol() , + status.internalStatus()+15 , + status.overlayIcons() + QStringList("msn_blocked") , + i18n("%1|Blocked").arg( status.description() ) ) ); + } + else if(!isBlocked() && status.internalStatus() >= 15) + { //the user is not blocked, but the status is blocked + switch(status.internalStatus()-15) + { + case 1: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->NLN); + break; + case 2: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->BSY); + break; + case 3: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->BRB); + break; + case 4: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->AWY); + break; + case 5: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->PHN); + break; + case 6: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->LUN); + break; + case 7: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->FLN); + break; + case 8: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->HDN); + break; + case 9: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->IDL); + break; + default: + Kopete::Contact::setOnlineStatus(MSNProtocol::protocol()->UNK); + break; + } + } + else + Kopete::Contact::setOnlineStatus(status); + m_currentStatus=status; +} + +void MSNContact::slotSendMail() +{ + MSNNotifySocket *notify = static_cast<MSNAccount*>( account() )->notifySocket(); + if( notify ) + { + notify->sendMail( contactId() ); + } +} + +void MSNContact::setDisplayPicture(KTempFile *f) +{ + //copy the temp file somewere else. + // in a better world, the file could be dirrectly wrote at the correct location. + // but the custom emoticon code is to deeply merged in the display picture code while it could be separated. + QString newlocation=locateLocal( "appdata", "msnpictures/"+ contactId().lower().replace(QRegExp("[./~]"),"-") +".png" ) ; + + KIO::Job *j=KIO::file_move( KURL::fromPathOrURL( f->name() ) , KURL::fromPathOrURL( newlocation ) , -1, true /*overwrite*/ , false /*resume*/ , false /*showProgressInfo*/ ); + + f->setAutoDelete(false); + delete f; + + //let the time to KIO to copy the file + connect(j, SIGNAL(result(KIO::Job *)) , this, SLOT(slotEmitDisplayPictureChanged() )); +} + +void MSNContact::slotEmitDisplayPictureChanged() +{ + QString newlocation=locateLocal( "appdata", "msnpictures/"+ contactId().lower().replace(QRegExp("[./~]"),"-") +".png" ) ; + setProperty( Kopete::Global::Properties::self()->photo() , newlocation ); + emit displayPictureChanged(); +} + +void MSNContact::setObject(const QString &obj) +{ + if(m_obj==obj && (obj.isEmpty() || hasProperty(Kopete::Global::Properties::self()->photo().key()))) + return; + + m_obj=obj; + + removeProperty( Kopete::Global::Properties::self()->photo() ) ; + emit displayPictureChanged(); + + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if ( config->readNumEntry( "DownloadPicture", 2 ) >= 2 && !obj.isEmpty() + && account()->myself()->onlineStatus().status() != Kopete::OnlineStatus::Invisible ) + manager(Kopete::Contact::CanCreate); //create the manager which will download the photo automatically. +} + +#include "msncontact.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msncontact.h b/kopete/protocols/msn/msncontact.h new file mode 100644 index 00000000..bcd6cc68 --- /dev/null +++ b/kopete/protocols/msn/msncontact.h @@ -0,0 +1,199 @@ +/* + msncontact.h - MSN Contact + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002 by Ryan Cumming <bodnar42@phalynx.dhs.org> + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNCONTACT_H +#define MSNCONTACT_H + +#include "kopetecontact.h" +#include "kopeteonlinestatus.h" + +#include <kurl.h> + +class QListView; +class QListViewItem; +class QPixmap; +class QTimer; + +class MSNChatSession; +class KAction; +class KActionCollection; +class KTempFile; + +namespace Kopete { class Protocol; } +namespace Kopete { class OnlineStatus; } + +class MSNContact : public Kopete::Contact +{ + Q_OBJECT + +public: + MSNContact( Kopete::Account *account, const QString &id, Kopete::MetaContact *parent ); + ~MSNContact(); + + /** + * Indicate whether this contact is blocked + */ + bool isBlocked() const; + void setBlocked( bool b ); + + /** + * Indicate whether this contact is deleted + * (not on the serverside list) + */ + bool isDeleted() const; + void setDeleted( bool d ); + + /** + * Indicate whether this contact is allowed + */ + bool isAllowed() const; + void setAllowed( bool d ); + + /** + * Indicate whether this contact is on the reversed list + */ + bool isReversed() const; + void setReversed( bool d ); + + /** + * set one phone number + */ + void setInfo(const QString &type, const QString &data); + + /** + * The groups in which the user is located on the server. + */ + const QMap<QString, Kopete::Group *> serverGroups() const; + /** + * clear that map + */ + void clearServerGroups(); + + /** + * client flags (say what version of msn messenger the contact is using) + */ + uint clientFlags() const; + void setClientFlags( uint ); + + virtual bool isReachable(); + + virtual QPtrList<KAction> *customContextMenuActions(); + + /** + * update the server group map + */ + void contactRemovedFromGroup( const QString& groupId ); + void contactAddedToGroup(const QString& groupId, Kopete::Group *group ); + + virtual void serialize( QMap<QString, QString> &serializedData, QMap<QString, QString> &addressBookData ); + + /** + * Rename contact on server + */ + virtual void rename( const QString &newName ) KDE_DEPRECATED; + + /** + * Returns the MSN Message Manager associated with this contact + */ + virtual Kopete::ChatSession *manager( Kopete::Contact::CanCreateFlags = Kopete::Contact::CannotCreate ); + + + /** + * Because blocked contact have a small auto-modified status + */ + void setOnlineStatus(const Kopete::OnlineStatus&); + + QString guid(); + QString phoneHome(); + QString phoneWork(); + QString phoneMobile(); + + void setObject(const QString &obj); + QString object() const { return m_obj; } + +public slots: + virtual void slotUserInfo(); + virtual void deleteContact(); + virtual void sendFile( const KURL &sourceURL = KURL(), + const QString &fileName = QString::null, uint fileSize = 0L ); + + /** + * Every time the kopete's contactlist is modified, we sync the serverlist with it + */ + virtual void sync( unsigned int cvhanged= 0xff); + + + void setDisplayPicture(KTempFile *f) ; + +signals: + void displayPictureChanged(); + +private slots: + void slotBlockUser(); + void slotShowProfile(); + void slotSendMail(); + void slotEmitDisplayPictureChanged(); + + /** + * Workaround to make this checkboxe readonly + */ + void slotUserInfoDialogReversedToggled(); + +private: + QMap<QString, Kopete::Group *> m_serverGroups; + + bool m_blocked; + bool m_allowed; + bool m_deleted; + bool m_reversed; + bool m_moving; + bool m_phone_mob; + + uint m_clientFlags; + + QString m_phoneHome; + QString m_phoneWork; + QString m_phoneMobile; + + + KAction *actionBlock; + KAction *actionShowProfile; + KAction *actionSendMail; + KAction *actionWebcamReceive; + KAction *actionWebcamSend; + + QString m_obj; //the MSNObject + + /** + * keep the current status here. (it's normally already in Kopete::Contact::d->onlineStatus) + * This is a workaround to prevent problems with the account offline status. + */ + Kopete::OnlineStatus m_currentStatus; + + //MSNProtocol::deserializeContact need to acess some contact insternals + friend class MSNProtocol; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msndebugrawcmddlg.cpp b/kopete/protocols/msn/msndebugrawcmddlg.cpp new file mode 100644 index 00000000..341c6808 --- /dev/null +++ b/kopete/protocols/msn/msndebugrawcmddlg.cpp @@ -0,0 +1,73 @@ +/* + msndebugrawcmddlg.cpp - Send a raw MSN command for debugging + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msndebugrawcmddlg.h" + +#include "ui/msndebugrawcommand_base.h" + +#include <qcheckbox.h> +#include <qlineedit.h> +#include <ktextedit.h> + +#include <klocale.h> + +MSNDebugRawCmdDlg::MSNDebugRawCmdDlg( QWidget *parent ) +: KDialogBase( parent, 0L, true, + i18n( "DEBUG: Send Raw Command - MSN Plugin" ), Ok | Cancel, + Ok, true ) +{ + setInitialSize( QSize( 350, 200 ) ); + + m_main = new MSNDebugRawCommand_base( this ); + setMainWidget( m_main ); +} + +MSNDebugRawCmdDlg::~MSNDebugRawCmdDlg() +{ +} + +QString MSNDebugRawCmdDlg::command() +{ + return m_main->m_command->text(); +} + +QString MSNDebugRawCmdDlg::params() +{ + return m_main->m_params->text(); +} + +bool MSNDebugRawCmdDlg::addNewline() +{ + return m_main->m_addNewline->isChecked(); +} + +bool MSNDebugRawCmdDlg::addId() +{ + return m_main->m_addId->isChecked(); +} + +QString MSNDebugRawCmdDlg::msg() +{ + return m_main->m_msg->text(); +} + +#include "msndebugrawcmddlg.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msndebugrawcmddlg.h b/kopete/protocols/msn/msndebugrawcmddlg.h new file mode 100644 index 00000000..3721daae --- /dev/null +++ b/kopete/protocols/msn/msndebugrawcmddlg.h @@ -0,0 +1,53 @@ +/* + msndebugrawcmddlg.h - Send a raw MSN command for debugging + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNDEBUGRAWCMDDLG_H +#define MSNDEBUGRAWCMDDLG_H + +#include <kdialogbase.h> + +class MSNDebugRawCommand_base; + +/** + * @author Martijn Klingens <klingens@kde.org> + * + * Simple debugging help + */ +class MSNDebugRawCmdDlg : public KDialogBase +{ + Q_OBJECT + +public: + MSNDebugRawCmdDlg( QWidget *parent ); + ~MSNDebugRawCmdDlg(); + + QString command(); + QString params(); + bool addNewline(); + bool addId(); + QString msg(); + +private: + MSNDebugRawCommand_base *m_main; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnfiletransfersocket.cpp b/kopete/protocols/msn/msnfiletransfersocket.cpp new file mode 100644 index 00000000..54a09e4a --- /dev/null +++ b/kopete/protocols/msn/msnfiletransfersocket.cpp @@ -0,0 +1,481 @@ +/*************************************************************************** + msnfiletransfersocket.cpp - description + ------------------- + begin : mer jui 31 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ kde.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#include "msnfiletransfersocket.h" + +#include <stdlib.h> +#include <math.h> + +//qt +#include <qtimer.h> + +// kde +#include <kdebug.h> +#include <kserversocket.h> +#include <kbufferedsocket.h> +#include <kfiledialog.h> +#include <klocale.h> + +#include "kopetetransfermanager.h" +#include "kopetecontact.h" +#include "kopetemetacontact.h" +#include "msnchatsession.h" +#include "msnswitchboardsocket.h" +#include "msnnotifysocket.h" +#include "msnaccount.h" + +using namespace KNetwork; + +MSNFileTransferSocket::MSNFileTransferSocket(const QString &handle, Kopete::Contact *c,bool incoming, QObject* parent) + : MSNSocket(parent) , MSNInvitation(incoming, MSNFileTransferSocket::applicationID() , i18n("File Transfer - MSN Plugin")) +{ + m_handle=handle; + m_kopeteTransfer=0l; + m_file=0L; + m_server=0L; + m_contact=c; + ready=true; + + QObject::connect( this, SIGNAL( socketClosed() ), this, SLOT( slotSocketClosed() ) ); + QObject::connect( this, SIGNAL( blockRead( const QByteArray & ) ), this, SLOT(slotReadBlock( const QByteArray & ) ) ); +} + +MSNFileTransferSocket::~MSNFileTransferSocket() +{ + delete m_file; + delete m_server; + kdDebug(14140) << "MSNFileTransferSocket::~MSNFileTransferSocket" <<endl; +} + +void MSNFileTransferSocket::parseCommand(const QString & cmd, uint id, const QString & data) +{ + if( cmd == "VER" ) + { + if(data.section( ' ', 0, 0 ) != "MSNFTP") + { + kdDebug(14140) << "MSNFileTransferSocket::parseCommand (VER): bad version: disconnect" <<endl; + disconnect(); + } + else + { + if( m_incoming ) + sendCommand( "USR", m_handle + " " + m_authcook, false ); + else + sendCommand( "VER", "MSNFTP" , false ); + } + } + else if( cmd == "FIL" ) + { + m_size=id; //data.toUInt(); //BUG: the size is take as id bye MSNSocket because it is a number + + m_downsize=0; + m_file=new QFile(m_fileName); + + if( m_file->open( IO_WriteOnly )) + sendCommand( "TFR" ,NULL,false); + else + { + kdDebug(14140) << "MSNFileTransferSocket::parseCommand: ERROR: unable to open file - disconnect " <<endl; + disconnect(); + } + } + else if( cmd == "BYE" ) + { + kdDebug(14140) << "MSNFileTransferSocket::parseCommand : end of transfer " <<endl; + disconnect(); + } + else if( cmd == "USR" ) + { + if(data.section( ' ', 1, 1 )!= m_authcook) + { + kdDebug(14140) << "MSNFileTransferSocket::parseCommand (USR): bad auth" <<endl; + disconnect(); + } + else + sendCommand("FIL" , QString::number(size()) , false); + } + else if( cmd == "TFR" ) + { + m_downsize=0; + ready=true; + QTimer::singleShot( 0, this, SLOT(slotSendFile()) ); + } + else if( cmd == "CCL" ) + { + disconnect(); + } + else + kdDebug(14140) << "MSNFileTransferSocket::parseCommand : unknown command " <<cmd <<endl; + +// kdDebug(14140) << "MSNFileTransferSocket::parseCommand : done " <<cmd <<endl; +} + +void MSNFileTransferSocket::doneConnect() +{ + if(m_incoming) + sendCommand( "VER", "MSNFTP", false ); + MSNSocket::doneConnect(); +} + +void MSNFileTransferSocket::bytesReceived(const QByteArray & head) +{ + if(head[0]!='\0') + { + kdDebug(14140) << "MSNFileTransferSocket::bytesReceived: transfer aborted" <<endl; + QTimer::singleShot(0,this,SLOT(disconnect())); + } + unsigned int sz=(int)((unsigned char)head.data()[2])*256+(int)((unsigned char)head.data()[1]); +// kdDebug(14140) << "MSNFileTransferSocket::bytesReceived: " << sz <<endl; + readBlock(sz); +} + +void MSNFileTransferSocket::slotSocketClosed() +{ + kdDebug(14140) << "MSNFileTransferSocket::slotSocketClose "<< endl; + if(m_file) + m_file->close(); + delete m_file; + m_file=0L; + delete m_server; + m_server=0L; + if(m_kopeteTransfer) + { + if( (m_downsize!=m_size || m_downsize==0 ) ) + m_kopeteTransfer->slotError( KIO::ERR_UNKNOWN , i18n( "An unknown error occurred" ) ); + else + m_kopeteTransfer->slotComplete(); + } + emit done(this); +} + +void MSNFileTransferSocket::slotReadBlock(const QByteArray &block) +{ + m_file->writeBlock( block.data(), block.size() ); // write to file + + m_downsize+=block.size(); + if(m_kopeteTransfer) m_kopeteTransfer->slotProcessed(m_downsize); + kdDebug(14140) << "MSNFileTransferSocket - " << m_downsize << " of " << m_size <<" done"<<endl; + + if(m_downsize==m_size) + { + //the transfer seems to be finished. + sendCommand( "BYE" ,"16777989",false); + // if we are not already disconected in 30 seconds, do it. + QTimer::singleShot( 30000 , this, SLOT(disconnect() ) ); + + } +} + +void MSNFileTransferSocket::setKopeteTransfer(Kopete::Transfer *kt) +{ + m_kopeteTransfer=kt; + if(kt) + { + QObject::connect(kt , SIGNAL(transferCanceled()), this, SLOT(abort())); + QObject::connect(kt, SIGNAL(destroyed()) , this , SLOT(slotKopeteTransferDestroyed())); + } +} + +void MSNFileTransferSocket::listen(int port) +{ + m_server = new KServerSocket(); + + QObject::connect( m_server, SIGNAL(readyAccept()), this, SLOT(slotAcceptConnection())); + m_server->setAddress(QString::number(port)); + + kdDebug(14140) << "MSNFileTransferSocket::listen: about to listen"<<endl; + bool listenResult = m_server->listen(1); + kdDebug(14140) << "MSNFileTransferSocket::listen: result: "<< listenResult <<endl; + QTimer::singleShot( 60000, this, SLOT(slotTimer()) ); + kdDebug(14140) << "MSNFileTransferSocket::listen done" <<endl; +} + +void MSNFileTransferSocket::slotAcceptConnection() +{ + kdDebug(14140) << "MSNFileTransferSocket::slotAcceptConnection" <<endl; + if(!accept(m_server)) + { + if( m_kopeteTransfer) + m_kopeteTransfer->slotError( KIO::ERR_UNKNOWN , i18n( "An unknown error occurred" ) ); + emit done(this); + } +} + +void MSNFileTransferSocket::slotTimer() +{ + if(onlineStatus() != Disconnected) + return; + kdDebug(14140) << "MSNFileTransferSocket::slotTimer: timeout "<< endl; + if( m_kopeteTransfer) + { + m_kopeteTransfer->slotError( KIO::ERR_CONNECTION_BROKEN , i18n( "Connection timed out" ) ); + } + + MSNChatSession* manager=dynamic_cast<MSNChatSession*>(m_contact->manager()); + if(manager && manager->service()) + manager->service()->sendCommand( "MSG" , "N", true, rejectMessage("TIMEOUT") ); + + emit done(this); +} + +void MSNFileTransferSocket::abort() +{ + if(m_incoming) + { + sendCommand( "CCL" , NULL ,false); + } + else + { + QByteArray bytes(3); + bytes[0]='\1'; + bytes[1]='\0'; + bytes[2]='\0'; + sendBytes( bytes ); + m_downsize=m_size; //we don't want to send data anymore; + } + //the timer wait one second, the time to send the CCL or the binary header + //retarding the disconnection keep away from a crash. (in KIO::Job::emitResult when `delete this`) + QTimer::singleShot( 1000, this, SLOT(disconnect()) ); + ready=false; +} + +void MSNFileTransferSocket::setFile( const QString &fn, long unsigned int fileSize ) +{ + m_fileName=fn; + if(!m_incoming) + { + if(m_file) + { + kdDebug(14140) << "MSNFileTransferSocket::setFileName: WARNING m_file already exists" << endl; + delete m_file; + } + m_file = new QFile( fn ); + if(!m_file->open(IO_ReadOnly)) + { + //FIXME: abort transfer here + kdDebug(14140) << "MSNFileTransferSocket::setFileName: WARNING unable to open the file" << endl; + } + + //If the fileSize is 0 it was not given, we are to get it from the file + if(fileSize == 0L) + m_size = m_file->size(); + else + m_size = fileSize; + } +} + + +void MSNFileTransferSocket::slotSendFile() +{ +// kdDebug(14140) <<"MSNFileTransferSocket::slotSendFile()" <<endl; + if( m_downsize >= m_size) + { + //the transfer seems to be finished. + // if we are not already disconected in 30 seconds, do it. + QTimer::singleShot( 30000 , this, SLOT(disconnect() ) ); + return; + } + + if(ready) + { + char data[2046]; + int bytesRead = m_file->readBlock( data, 2045 ); + + QByteArray block(bytesRead+3); +// char i1= (char)fmod( bytesRead, 256 ) ; +// char i2= (char)floor( bytesRead / 256 ) ; +// kdDebug(14140) << "MSNFileTransferSocket::slotSendFile: " << (int)i1 <<" + 256* "<< (int)i2 <<" = " << bytesRead <<endl; + block[0]='\0'; + block[1]= (char)fmod( bytesRead, 256 ); + block[2]= (char)floor( bytesRead / 256 ); + + for ( int f = 0; f < bytesRead; f++ ) + { + block[f+3] = data[f]; + } + + sendBytes(block); + + m_downsize+=bytesRead; + if(m_kopeteTransfer) + m_kopeteTransfer->slotProcessed(m_downsize); + kdDebug(14140) << "MSNFileTransferSocket::slotSendFile: " << m_downsize << " of " << m_size <<" done"<<endl; + } + ready=false; + + QTimer::singleShot( 10, this, SLOT(slotSendFile()) ); +} + +void MSNFileTransferSocket::slotReadyWrite() +{ + ready=true; + MSNSocket::slotReadyWrite(); +} + +QString MSNFileTransferSocket::invitationHead() +{ + QTimer::singleShot( 10 * 60000, this, SLOT(slotTimer()) ); //the user has 10 mins to accept or refuse or initiate the transfer + + return QString( MSNInvitation::invitationHead()+ + "Application-File: "+ m_fileName.right( m_fileName.length() - m_fileName.findRev( '/' ) - 1 ) +"\r\n" + "Application-FileSize: "+ QString::number(size()) +"\r\n\r\n").utf8(); +} + +void MSNFileTransferSocket::parseInvitation(const QString& msg) +{ + QRegExp rx("Invitation-Command: ([A-Z]*)"); + rx.search(msg); + QString command=rx.cap(1); + if( msg.contains("Invitation-Command: INVITE") ) + { + rx=QRegExp("Application-File: ([^\\r\\n]*)"); + rx.search(msg); + QString filename = rx.cap(1); + rx=QRegExp("Application-FileSize: ([0-9]*)"); + rx.search(msg); + unsigned long int filesize= rx.cap(1).toUInt(); + + MSNInvitation::parseInvitation(msg); //for the cookie + + Kopete::TransferManager::transferManager()->askIncomingTransfer( m_contact , filename, filesize, QString::null, QString::number( cookie() ) ); + + QObject::connect( Kopete::TransferManager::transferManager(), SIGNAL( accepted( Kopete::Transfer *, const QString& ) ),this, SLOT( slotFileTransferAccepted( Kopete::Transfer *, const QString& ) ) ); + QObject::connect( Kopete::TransferManager::transferManager(), SIGNAL( refused( const Kopete::FileTransferInfo & ) ), this, SLOT( slotFileTransferRefused( const Kopete::FileTransferInfo & ) ) ); + + } + else if( msg.contains("Invitation-Command: ACCEPT") ) + { + if(incoming()) + { + rx=QRegExp("IP-Address: ([0-9\\.]*)"); + rx.search(msg); + QString ip_address = rx.cap(1); + rx=QRegExp("AuthCookie: ([0-9]*)"); + rx.search(msg); + QString authcook = rx.cap(1); + rx=QRegExp("Port: ([0-9]*)"); + rx.search(msg); + QString port = rx.cap(1); + + setAuthCookie(authcook); + connect(ip_address, port.toUInt()); + } + else + { + unsigned long int auth = (rand()%(999999))+1; + setAuthCookie(QString::number(auth)); + + setKopeteTransfer(Kopete::TransferManager::transferManager()->addTransfer(m_contact, fileName(), size(), m_contact->metaContact() ? m_contact->metaContact()->displayName() : m_contact->contactId() , Kopete::FileTransferInfo::Outgoing)); + + MSNChatSession* manager=dynamic_cast<MSNChatSession*>(m_contact->manager()); + if(manager && manager->service()) + { + MSNNotifySocket *notify=static_cast<MSNAccount*>(manager->account())->notifySocket(); + if(notify){ + + QCString message=QString( + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: " + QString::number(cookie()) + "\r\n" + "IP-Address: " + notify->localIP() + "\r\n" + "Port: 6891\r\n" + "AuthCookie: "+QString::number(auth)+"\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n\r\n").utf8(); + + manager->service()->sendCommand( "MSG" , "N", true, message ); + } + } + + listen(6891); + } + } + else //CANCEL + { + MSNInvitation::parseInvitation(msg); + if( m_kopeteTransfer) + m_kopeteTransfer->slotError( KIO::ERR_ABORTED , i18n( "The remote user aborted" ) ); + emit done(this); + + } +} + +void MSNFileTransferSocket::slotFileTransferAccepted(Kopete::Transfer *trans, const QString& fileName) +{ + if(trans->info().internalId().toULong() != cookie()) + return; + + if(!trans->info().contact()) + return; + + setKopeteTransfer(trans); + + MSNChatSession* manager=dynamic_cast<MSNChatSession*>(m_contact->manager()); + + if(manager && manager->service()) + { + setFile(fileName); + + QCString message=QString( + "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: ACCEPT\r\n" + "Invitation-Cookie: " + QString::number(cookie()) + "\r\n" + "Launch-Application: FALSE\r\n" + "Request-Data: IP-Address:\r\n" ).utf8(); + manager->service()->sendCommand( "MSG" , "N", true, message ); + + QTimer::singleShot( 3 * 60000, this, SLOT(slotTimer()) ); //if after 3 minutes the transfer has not begin, delete this + } + else + { + if( m_kopeteTransfer) + m_kopeteTransfer->slotError( KIO::ERR_UNKNOWN , i18n( "An unknown error occurred" ) ); + emit done(this); + + } +} + +void MSNFileTransferSocket::slotFileTransferRefused(const Kopete::FileTransferInfo &info) +{ + if(info.internalId().toULong() != cookie()) + return; + + if(!info.contact()) + return; + + MSNChatSession* manager=dynamic_cast<MSNChatSession*>(m_contact->manager()); + + if(manager && manager->service()) + { + manager->service()->sendCommand( "MSG" , "N", true, rejectMessage() ); + } + + emit done(this); +} + +void MSNFileTransferSocket::slotKopeteTransferDestroyed() +{ + m_kopeteTransfer=0L; +} + + +#include "msnfiletransfersocket.moc" + diff --git a/kopete/protocols/msn/msnfiletransfersocket.h b/kopete/protocols/msn/msnfiletransfersocket.h new file mode 100644 index 00000000..82ae0662 --- /dev/null +++ b/kopete/protocols/msn/msnfiletransfersocket.h @@ -0,0 +1,119 @@ +/*************************************************************************** + msnfiletransfersocket.h - description + ------------------- + begin : mer jui 31 2002 + copyright : (C) 2002 by Olivier Goffart + email : ogoffart @ kde.org + ***************************************************************************/ + +/*************************************************************************** + * * + * 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. * + * * + ***************************************************************************/ + +#ifndef MSNFILETRANSFERSOCKET_H +#define MSNFILETRANSFERSOCKET_H + +#include <qwidget.h> + +#include "msnsocket.h" +#include "msninvitation.h" + +class QFile; + +namespace KNetwork { + class KServerSocket; +} + +namespace Kopete { class Transfer; } +namespace Kopete { class FileTransferInfo; } +namespace Kopete { class Protocol; } +namespace Kopete { class Contact; } + +/** + * @author Olivier Goffart + */ +class MSNFileTransferSocket : public MSNSocket , public MSNInvitation +{ + Q_OBJECT + +public: + MSNFileTransferSocket(const QString &myID,Kopete::Contact* c, bool incoming, QObject* parent = 0L ); + ~MSNFileTransferSocket(); + + static QString applicationID() { return "5D3E02AB-6190-11d3-BBBB-00C04F795683"; } + QString invitationHead(); + + + void setKopeteTransfer( Kopete::Transfer *kt ); + Kopete::Transfer* kopeteTransfer() { return m_kopeteTransfer; } + void setFile( const QString &fn, long unsigned int fileSize = 0L ); + void setAuthCookie( const QString &c ) { m_authcook = c; } + QString fileName() { return m_fileName;} + long unsigned int size() { return m_size;} + void listen( int port ); + + virtual void parseInvitation(const QString& invitation); + + virtual QObject* object() { return this; } + +public slots: + void abort(); + +signals: + void done( MSNInvitation * ); + +protected: + /** + * This reimplementation sets up the negotiating with the server and + * suppresses the change of the status to online until the handshake + * is complete. + */ + virtual void doneConnect(); + + /** + * Handle an MSN command response line. + */ + virtual void parseCommand(const QString & cmd, uint id, const QString & data); + virtual void bytesReceived(const QByteArray & data); + +protected slots: + virtual void slotReadyWrite(); + +private slots: + void slotSocketClosed(); + void slotReadBlock(const QByteArray &); + void slotAcceptConnection(); + void slotTimer(); + void slotSendFile(); + + void slotFileTransferRefused( const Kopete::FileTransferInfo &info ); + void slotFileTransferAccepted( Kopete::Transfer *trans, const QString& fileName ); + /* the Kopete::Transfer has been deleted */ + void slotKopeteTransferDestroyed(); + + +private: + QString m_handle; + Kopete::Contact *m_contact; + + long unsigned int m_size; + long unsigned int m_downsize; + QString m_authcook; + QString m_fileName; + Kopete::Transfer* m_kopeteTransfer; + QFile *m_file ; + KNetwork::KServerSocket *m_server; + + bool ready; + +}; + +#endif + +// vim: set noet ts=4 sts=4 tw=4: + diff --git a/kopete/protocols/msn/msninvitation.cpp b/kopete/protocols/msn/msninvitation.cpp new file mode 100644 index 00000000..ddc8136a --- /dev/null +++ b/kopete/protocols/msn/msninvitation.cpp @@ -0,0 +1,100 @@ +/* + msninvitation.cpp + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "msninvitation.h" +#include <stdlib.h> +#include <qregexp.h> + +MSNInvitation::MSNInvitation(bool incoming, const QString &applicationID , const QString &applicationName) +{ + m_incoming=incoming; + m_applicationId=applicationID; + m_applicationName=applicationName; + m_cookie= (rand()%(999999))+1; + m_state = Nothing; +} + + +MSNInvitation::~MSNInvitation() +{ +} + +QCString MSNInvitation::unimplemented(long unsigned int cookie) +{ + return QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: CANCEL\r\n" + "Cancel-Code: REJECT_NOT_INSTALLED\r\n" + "Invitation-Cookie: " + QString::number(cookie) + "\r\n" + "Session-ID: {120019D9-C3F5-4F94-978D-CB33534C3309}\r\n\r\n").utf8(); + //FIXME: i don't know at all what Seession-ID is +} + +QString MSNInvitation::invitationHead() +{ + setState(Invited); + return QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Application-Name: " + m_applicationName + "\r\n" + "Application-GUID: {" + m_applicationId + "}\r\n" + "Invitation-Command: INVITE\r\n" + "Invitation-Cookie: " +QString::number(m_cookie) +"\r\n"); +} + +QCString MSNInvitation::rejectMessage(const QString & rejectcode) +{ + return QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgsinvite; charset=UTF-8\r\n" + "\r\n" + "Invitation-Command: CANCEL\r\n" + "Invitation-Cookie: " + QString::number(cookie()) + "\r\n" + "Cancel-Code: "+ rejectcode +"\r\n").utf8(); +} + +void MSNInvitation::parseInvitation(const QString& msg) +{ + QRegExp rx("Invitation-Command: ([A-Z]*)"); + rx.search(msg); + QString command=rx.cap(1); + + if(command=="INVITE") + { + rx=QRegExp("Invitation-Cookie: ([0-9]*)"); + rx.search(msg); + m_cookie=rx.cap(1).toUInt(); + } + else if(command=="CANCEL") + { + /*rx=QRegExp("Cancel-Code: ([0-9]*)"); + rx.search(msg); + QString code=rx.cap(1).toUInt(); + //TODO: parse the code*/ + } +// else if(command=="ACCEPT") +} + +MSNInvitation::State MSNInvitation::state() +{ + return m_state; +} + +void MSNInvitation::setState(MSNInvitation::State s) +{ + m_state=s; +} + diff --git a/kopete/protocols/msn/msninvitation.h b/kopete/protocols/msn/msninvitation.h new file mode 100644 index 00000000..90010dc3 --- /dev/null +++ b/kopete/protocols/msn/msninvitation.h @@ -0,0 +1,126 @@ +/* + msninvitation.cpp + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + + Kopete (c) 2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef MSNINVITATION_H +#define MSNINVITATION_H + +#include <qstring.h> + +#include "kopete_export.h" + +class QObject; + +/** + * @author Olivier Goffart + * + * The invitation is the base class which handle an MSN invitation. + * The implemented class must to herits from QObject too. + * You can accept the invitation by catching @ref MSNProtocol::invitation() signals + * or create one and insert it to a kmm with @ref MSNChatSession::initInvitation() + * you can add action with @ref Kopete::Plugin::customChatActions() + */ +class KOPETE_EXPORT MSNInvitation +{ +public: + /** + * Constructor + * @param incoming say if it is an incoming invitation + * @param applicationID is the exadecimal id of the invitation + * @param applicationName is a i18n'ed string of the name of the application + */ + MSNInvitation(bool incoming,const QString &applicationID , const QString &applicationName); + virtual ~MSNInvitation(); + + /** + * @internal + * it is a reject invitation because the invitation is not implemented + */ + static QCString unimplemented(long unsigned int cookie); + + /** + * you can set manualy the cookie. note that a cookie is automatically generated when a new + * invitation is created, or in @ref parseInvitation + */ + void setCookie( long unsigned int c ) { m_cookie = c; } + /** + * @return the cookie + */ + long unsigned int cookie() { return m_cookie; } + + /** + * @return true if it is an incommijng invitation + */ + bool incoming() { return m_incoming; } + + + /** + * reimplement this. this is the invitation string used in @ref MSNChatSession::initInvitation() + * the default implementation return the common begin. + * You can also set the state to Invited (the default implementation do that) + */ + virtual QString invitationHead(); + + /** + * This is the reject invitation string + * @param rejectcode is the code, it can be "REJECT" or "TIMEOUT" + */ + QCString rejectMessage(const QString & rejectcode = "REJECT"); + + /** + * reimplement this method. it is called when an invitation message with the invitation's cookie is received + * the default implementation parse the cookie, or the reject message + */ + virtual void parseInvitation(const QString& invitation); + + /** + * return the qobject (this) + */ + virtual QObject* object()=0; +//signals: + /** + * reimplement this as a signal, and emit it when the invitation has to be destroyed. + * don't delete the invitation yourself + */ + virtual void done(MSNInvitation*)=0; + + /** + * This indiquate the state. it is going to be completed later + * - Nothing means than nothing has been done in the invitaiton (nothing has been sent/received) + * - Invited means than the invitaiton has been sent + */ + enum State { Nothing=0 , Invited=1 }; + /** + * retrun the current state + */ + State state(); + /** + * set the current State + */ + void setState(State); + + + +protected: + bool m_incoming; + long unsigned int m_cookie; + QString m_applicationId; + QString m_applicationName; + State m_state; + + +}; + +#endif diff --git a/kopete/protocols/msn/msnnotifysocket.cpp b/kopete/protocols/msn/msnnotifysocket.cpp new file mode 100644 index 00000000..e31e0257 --- /dev/null +++ b/kopete/protocols/msn/msnnotifysocket.cpp @@ -0,0 +1,1309 @@ +/* + msnnotifysocket.cpp - Notify Socket for the MSN Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + Portions taken from + KMerlin (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnnotifysocket.h" +#include "msncontact.h" +#include "msnaccount.h" +#include "msnsecureloginhandler.h" +#include "msnchallengehandler.h" + +#include <qdatetime.h> +#include <qregexp.h> +#include <qdom.h> + +#include <kdebug.h> +#include <kdeversion.h> +#include <klocale.h> +#include <kmdcodec.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <ktempfile.h> +#include <krun.h> +#include <kio/job.h> +#include <qfile.h> +#include <kconfig.h> +#include <knotification.h> + +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" + +#include <ctime> + + +MSNNotifySocket::MSNNotifySocket( MSNAccount *account, const QString& /*msnId*/, const QString &password ) +: MSNSocket( account ) +{ + m_newstatus = MSNProtocol::protocol()->NLN; + m_secureLoginHandler=0L; + m_challengeHandler = 0L; + + m_isHotmailAccount=false; + m_ping=false; + m_disconnectReason=Kopete::Account::Unknown; + + m_account = account; + m_password=password; + QObject::connect( this, SIGNAL( blockRead( const QByteArray & ) ), + this, SLOT( slotReadMessage( const QByteArray & ) ) ); + m_keepaliveTimer = 0L; + m_isLogged = false; +} + +MSNNotifySocket::~MSNNotifySocket() +{ + delete m_secureLoginHandler; + delete m_challengeHandler; + + kdDebug(14140) << k_funcinfo << endl; +} + +void MSNNotifySocket::doneConnect() +{ +// kdDebug( 14140 ) << k_funcinfo << "Negotiating server protocol version" << endl; + sendCommand( "VER", "MSNP11 MSNP10 CVR0" ); +} + + +void MSNNotifySocket::disconnect() +{ + m_isLogged = false; + if( m_disconnectReason==Kopete::Account::Unknown ) + m_disconnectReason=Kopete::Account::Manual; + if( onlineStatus() == Connected ) + sendCommand( "OUT", QString::null, false ); + + if( m_keepaliveTimer ) + m_keepaliveTimer->stop(); + + // the socket is not connected yet, so I should force the signals + if ( onlineStatus() == Disconnected || onlineStatus() == Connecting ) + emit socketClosed(); + else + MSNSocket::disconnect(); +} + +void MSNNotifySocket::handleError( uint code, uint id ) +{ + kdDebug(14140) << k_funcinfo << endl; + + QString handle; + if(m_tmpHandles.contains(id)) + handle=m_tmpHandles[id]; + + QString msg; + MSNSocket::ErrorType type; + // See http://www.hypothetic.org/docs/msn/basics.php for a + // description of all possible error codes. + // TODO: Add support for all of these! + switch( code ) + { + case 201: + case 205: + case 208: + { + msg = i18n( "<qt>The MSN user '%1' does not exist.<br>Please check the MSN ID.</qt>" ).arg( handle ); + type = MSNSocket::ErrorServerError; + break; + } + case 207: + case 218: + case 540: + { + msg = i18n( "<qt>An internal error occurred in the MSN plugin.<br>" + "MSN Error: %1<br>" + "please send us a detailed bug report " + "at kopete-devel@kde.org containing the raw debug output on the " + "console (in gzipped format, as it is probably a lot of output.)" ).arg(code); + type = MSNSocket::ErrorServerError; + break; + } + case 209: + { + if(handle==m_account->accountId()) + { + msg = i18n( "Unable to change your display name.\n" + "Please ensure your display is not too long and does not contains censored words." ); + type = MSNSocket::ErrorServerError; + } + /*else + { + QString msg = i18n( "You are trying to change the display name of a user who has not " + "confirmed his or her email address;\n" + "the contact was not renamed on the server." ); + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Error, msg, i18n( "MSN Plugin" ) ); + }*/ + break; + } + case 210: + { + msg = i18n("Your contact list is full; you cannot add any new contacts."); + type = MSNSocket::ErrorServerError; + break; + } + case 215: + { + msg = i18n( "<qt>The user '%1' already exists in this group on the MSN server;<br>" + "if Kopete does not show the user, please send us a detailed bug report " + "at kopete-devel@kde.org containing the raw debug output on the " + "console (in gzipped format, as it is probably a lot of output.)</qt>" ).arg(handle); + type = MSNSocket::ErrorInformation; + break; + } + case 216: + { + //This might happen is you rename an user if he is not in the contactlist + //currently, we just iniore; + //TODO: try to don't rename user not in the list + //actualy, the bug is in MSNChatSession::slotUserJoined() + break; + } + case 219: + { + msg = i18n( "The user '%1' seems to already be blocked or allowed on the server." ).arg(handle); + type = MSNSocket::ErrorServerError; + break; + } + case 223: + { + msg = i18n( "You have reached the maximum number of groups:\n" + "MSN does not support more than 30 groups." ); + type = MSNSocket::ErrorServerError; + break; + } + case 224: + case 225: + case 230: + { + msg = i18n("Kopete is trying to perform an operation on a group or a contact that does not exists on the server.\n" + "This might happen if the Kopete contact list and the MSN-server contact list are not correctly synchronized; if this is the case, you probably should send a bug report."); + type = MSNSocket::ErrorServerError; + break; + } + + case 229: + { + msg = i18n("The group name is too long; it has not been changed on the MSN server."); + type = MSNSocket::ErrorServerError; + break; + } + case 710: + { + msg = i18n( "You cannot open a Hotmail inbox because you do not have an MSN account with a valid " + "Hotmail or MSN mailbox." ); + type = MSNSocket::ErrorServerError; + break; + } + case 715: + { + /* + //if(handlev==m_account->accountId()) + QString msg = i18n( "Your email address has not been verified with the MSN server.\n" + "You should have received a mail with a link to confirm your email address.\n" + "Some functions will be restricted if you do not confirm your email address." ); + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, msg, i18n( "MSN Plugin" ) );//TODO don't show again + */ + break; + } + case 800: + { + //This happen when too much commends are sent to the server. + //the command will not be executed, too bad. + // ignore it for now, as we don't really know what command it was. + /* QString msg = i18#n( "You are trying to change your status, or your display name too rapidly.\n" + "This might happen if you added yourself to your own contact list." ); + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, msg, i18n( "MSN Plugin" ) ); + //FIXME: try to fix this problem*/ + break; + } + case 911: + m_disconnectReason=Kopete::Account::BadPassword; + disconnect(); + break; + case 913: + { + msg = i18n( "You can not send messages when you are offline or when you are invisible." ); + type = MSNSocket::ErrorServerError; + break; + } + case 923: + { + msg = i18n( "You are trying to perform an action you are not allowed to perform in 'kid mode'." ); + type = MSNSocket::ErrorServerError; + break; + } + + default: + MSNSocket::handleError( code, id ); + break; + } + + if( !msg.isEmpty() ) + emit errorMessage( type, msg ); +} + +void MSNNotifySocket::parseCommand( const QString &cmd, uint id, const QString &data ) +{ + //kdDebug(14140) << "MSNNotifySocket::parseCommand: Command: " << cmd << endl; + + if ( cmd == "VER" ) + { + sendCommand( "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 7.0.0816 MSMSGS " + m_account->accountId() ); +/* + struct utsname utsBuf; + uname ( &utsBuf ); + + sendCommand( "CVR", i18n( "MS Local code, see http://www.microsoft.com/globaldev/reference/oslocversion.mspx", "0x0409" ) + + " " + escape( utsBuf.sysname ) + " " + escape( utsBuf.release ) + " " + escape( utsBuf.machine ) + " Kopete " + + escape( kapp->aboutData()->version() ) + " Kopete " + m_msnId ); +*/ + } + else if ( cmd == "CVR" ) //else if ( cmd == "INF" ) + { + sendCommand( "USR", "TWN I " + m_account->accountId() ); + } + else if( cmd == "USR" ) //// here follow the auth processus + { + if( data.section( ' ', 1, 1 ) == "S" ) + { + m_secureLoginHandler = new MSNSecureLoginHandler(m_account->accountId(), m_password, data.section( ' ' , 2 , 2 )); + + QObject::connect(m_secureLoginHandler, SIGNAL(loginFailed()), this, SLOT(sslLoginFailed())); + QObject::connect(m_secureLoginHandler, SIGNAL(loginBadPassword()), this, SLOT(sslLoginIncorrect())); + QObject::connect(m_secureLoginHandler, SIGNAL(loginSuccesful(QString )), this, SLOT(sslLoginSucceeded(QString ))); + + m_secureLoginHandler->login(); + } + else + { + // Successful authentication. + m_disconnectReason=Kopete::Account::Unknown; + + // Synchronize with the server. + QString lastSyncTime, lastChange; + + if(m_account->contacts().count() > 1) + { + // Retrieve the last synchronization timestamp, and last change timestamp. + lastSyncTime = m_account->configGroup()->readEntry("lastsynctime", "0"); + lastChange = m_account->configGroup()->readEntry("lastchange", "0"); + } + else + { + //the contactliust has maybe being removed, force to sync + //(the only contact is myself) + lastSyncTime="0"; + lastChange="0"; + } + + sendCommand( "SYN", lastChange + " " + lastSyncTime); + // Get client features. + if(!useHttpMethod()) { + sendCommand( "GCF", "Shields.xml"); + // We are connected start to ping + slotSendKeepAlive(); + } + } + } + else if( cmd == "LST" ) + { + // MSNP11 changed command. Now it's: + // LST N=passport@hotmail.com F=Display%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 13 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // But can be + // LST N=passport@hotmail.com 10 + QString publicName, contactGuid, groups; + uint lists; + + QRegExp regex("N=([^ ]+)(?: F=([^ ]+))?(?: C=([0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}))? (\\d+)\\s?((?:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12},?)*)$"); + regex.search(data); + + // Capture passport email. + m_tmpLastHandle = regex.cap(1); + // Capture public name. + publicName = unescape( regex.cap(2) ); + // Capture contact guid. + contactGuid = regex.cap(3); + // Capture list enum type. + lists = regex.cap(4).toUInt(); + // Capture contact group(s) guid(s) + groups = regex.cap(5); + +// kdDebug(14140) << k_funcinfo << " msnId: " << m_tmpLastHandle << " publicName: " << publicName << " contactGuid: " << contactGuid << " list: " << lists << " groupGuid: " << groups << endl; + + // handle, publicName, Contact GUID, lists, Group GUID + emit contactList( m_tmpLastHandle , publicName, contactGuid, lists, groups ); + } + else if( cmd == "GCF" ) + { + m_configFile = data.section(' ', 0, 0); + readBlock( data.section( ' ', 1, 1 ).toUInt() ); + } + else if( cmd == "MSG" ) + { + readBlock( data.section( ' ', 2, 2 ).toUInt() ); + } + else if( cmd == "ILN" || cmd == "NLN" ) + { + // status handle publicName strangeNumber MSNOBJECT + MSNContact *c = static_cast<MSNContact*>( m_account->contacts()[ data.section( ' ', 1, 1 ) ] ); + if( c && c->contactId() != m_account->accountId() ) + { + QString publicName=unescape( data.section( ' ', 2, 2 ) ); + if ( (publicName!=c->contactId() || c->hasProperty(Kopete::Global::Properties::self()->nickName().key()) ) && + publicName!=c->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) + + changePublicName(publicName,c->contactId()); + QString obj=unescape(data.section( ' ', 4, 4 )); + c->setObject( obj ); + c->setOnlineStatus( convertOnlineStatus( data.section( ' ', 0, 0 ) ) ); + c->setClientFlags(data.section( ' ', 3, 3 ).toUInt()); + } + } + else if( cmd == "UBX" ) + { + m_tmpLastHandle = data.section(' ', 0, 0); + uint length = data.section( ' ', 1, 1 ).toUInt(); + if(length > 0) { + readBlock( length ); + } + } + else if( cmd == "UUX" ) + { + // UUX is sended to acknowledge that the server has received and processed the personal Message. + // if the result is 0, set the myself() contact personalMessage. + if( data.section(' ', 0, 0) == QString::fromUtf8("0") ) + m_account->myself()->setProperty(MSNProtocol::protocol()->propPersonalMessage, m_propertyPersonalMessage); + } + else if( cmd == "FLN" ) + { + MSNContact *c = static_cast<MSNContact*>( m_account->contacts()[ data.section( ' ', 0, 0 ) ] ); + if( c && c->contactId() != m_account->accountId() ) + { + c->setOnlineStatus( MSNProtocol::protocol()->FLN ); + c->removeProperty( MSNProtocol::protocol()->propClient ); + } + } + else if( cmd == "XFR" ) + { + QString stype=data.section( ' ', 0, 0 ); + if( stype=="SB" ) //switchboard connection (chat) + { + // Address, AuthInfo + emit startChat( data.section( ' ', 1, 1 ), data.section( ' ', 3, 3 ) ); + } + else if( stype=="NS" ) //notifysocket ; Got our notification server + { //we are connecting and we receive the initial NS, or the msn server encounter a problem, and we are switching to another switchboard + QString host = data.section( ' ', 1, 1 ); + QString server = host.section( ':', 0, 0 ); + uint port = host.section( ':', 1, 1 ).toUInt(); + setOnlineStatus( Connected ); + emit receivedNotificationServer( server, port ); + disconnect(); + } + + } + else if( cmd == "RNG" ) + { + // SessionID, Address, AuthInfo, handle, publicName + emit invitedToChat( QString::number( id ), data.section( ' ', 0, 0 ), data.section( ' ', 2, 2 ), + data.section( ' ', 3, 3 ), unescape( data.section( ' ', 4, 4 ) ) ); + } + else if( cmd == "ADC" ) + { + QString msnId, list, publicName, contactGuid, groupGuid; + + // Retrieve the list parameter (FL/AL/BL/RL) + list = data.section( ' ', 0, 0 ); + + // Examples of received data + // ADC TrID xL N=example@passport.com + // ADC TrID FL C=contactGuid groupdGuid + // ADC TrID RL N=example@passport.com F=friednly%20name + // ADC TrID FL N=ex@pas.com F=My%20Name C=contactGuid + // Thanks Gregg for that complex RegExp. + QRegExp regex("(?:N=([^ ]+))?(?: F=([^ ]+))?(?: C=([0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12}))?\\s?((?:[0-9a-fA-F]{8}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{4}\\-[0-9a-fA-F]{12},?)*)$"); + regex.search( data.section( ' ', 1 ) ); + + // Capture passport email. + msnId = regex.cap(1); + // Capture public name. + publicName = unescape( regex.cap(2) ); + // Capture contact guid. + contactGuid = regex.cap(3); + // Capture contact group(s) guid(s) + groupGuid = regex.cap(4); + +// kdDebug(14140) << k_funcinfo << list << " msnId: " << msnId << " publicName: " << publicName << " contactGuid: " << contactGuid << " groupGuid: " << groupGuid << endl; + + // handle, list, publicName, contactGuid, groupGuid + emit contactAdded( msnId, list, publicName, contactGuid, groupGuid ); + } + else if( cmd == "REM" ) // someone is removed from a list + { + QString handle, list, contactGuid, groupGuid; + list = data.section( ' ', 0, 0 ); + if( list == "FL" ) + { + // Removing a contact + if( data.contains( ' ' ) < 2 ) + { + contactGuid = data.section( ' ', 1, 1 ); + } + // Removing a contact from a group + else if( data.contains( ' ' ) < 3 ) + { + contactGuid = data.section( ' ', 1, 1 ); + groupGuid = data.section( ' ', 2, 2 ); + } + } + else + { + handle = data.section( ' ', 1, 1); + } + + // handle, list, contactGuid, groupGuid + emit contactRemoved( handle, list, contactGuid, groupGuid ); + + } + else if( cmd == "OUT" ) + { + if( data.section( ' ', 0, 0 ) == "OTH" ) + { + m_disconnectReason=Kopete::Account::OtherClient; + } + disconnect(); + } + else if( cmd == "CHG" ) + { + QString status = data.section( ' ', 0, 0 ); + setOnlineStatus( Connected ); + emit statusChanged( convertOnlineStatus( status ) ); + } + else if( cmd == "SBP" ) + { + QString contactGuid, type, publicName; + contactGuid = data.section( ' ', 0, 0 ); + type = data.section( ' ', 1, 1 ); + if(type == "MFN" ) + { + publicName = unescape( data.section( ' ', 2, 2 ) ); + MSNContact *c = m_account->findContactByGuid( contactGuid ); + if(c != 0L) + { + c->setProperty( Kopete::Global::Properties::self()->nickName(), publicName ); + } + } + } + else if( cmd == "LSG" ) + { + // New Format: LSG Friends xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + // groupDisplayName, groupGuid + emit groupListed( unescape( data.section( ' ', 0, 0 ) ), data.section( ' ', 1, 1) ); + } + else if( cmd == "ADG" ) + { + // groupName, groupGuid + emit groupAdded( unescape( data.section( ' ', 0, 0 ) ), + data.section( ' ', 1, 1 ) ); + } + else if( cmd == "REG" ) + { + // groupGuid, groupName + emit groupRenamed( data.section( ' ', 0, 0 ), unescape( data.section( ' ', 1, 1 ) ) ); + } + else if( cmd == "RMG" ) + { + // groupGuid + emit groupRemoved( data.section( ' ', 1, 1 ) ); + } + else if( cmd == "CHL" ) + { + m_challengeHandler = new MSNChallengeHandler("CFHUR$52U_{VIX5T", "PROD0101{0RM?UBW"); + // Compute the challenge response hash, and send the response. + QString chlResponse = m_challengeHandler->computeHash(data.section(' ', 0, 0)); + sendCommand("QRY", m_challengeHandler->productId(), true, chlResponse.utf8()); + // Dispose of the challenge handler. + m_challengeHandler->deleteLater(); + m_challengeHandler = 0L; + } + else if( cmd == "SYN" ) + { + // Retrieve the last synchronization timestamp known to the server. + QString lastSyncTime = data.section( ' ', 1, 1 ); + QString lastChange = data.section( ' ', 0, 0 ); + if( lastSyncTime != m_account->configGroup()->readEntry("lastsynctime") || + lastChange != m_account->configGroup()->readEntry("lastchange") ) + { + // If the server timestamp and the local timestamp are different, + // prepare to receive the contact list. + emit newContactList(); // remove all contacts datas, msn sends a new contact list + m_account->configGroup()->writeEntry( "lastsynctime" , lastSyncTime); + m_account->configGroup()->writeEntry( "lastchange", lastChange); + }else + kdDebug(14140) << k_funcinfo << "Contact list up-to-date." << endl; + + // set the status + setStatus( m_newstatus ); + } + else if( cmd == "BPR" ) + { + MSNContact *c = static_cast<MSNContact*>( m_account->contacts()[ m_tmpLastHandle ] ); + if( c ) + c->setInfo(data.section( ' ', 0, 0 ),unescape(data.section( ' ', 1, 1 ))); + } + else if( cmd == "PRP" ) + { + MSNContact *c = static_cast<MSNContact*>( m_account->myself() ); + if( c ) + { + QString type = data.section( ' ', 0, 0 ); + QString prpData = unescape( data.section( ' ', 1, 1 ) ); //SECURITY???????? + c->setInfo( type, prpData ); + m_account->configGroup()->writeEntry( type, prpData ); + } + } + else if( cmd == "BLP" ) + { + if( id > 0 ) //FROM BLP + { + m_account->configGroup()->writeEntry( "serial" , data.section( ' ', 0, 0 ) ); + m_account->configGroup()->writeEntry( "BLP" , data.section( ' ', 1, 1 ) ); + } + else //FROM SYN + m_account->configGroup()->writeEntry( "BLP" , data.section( ' ', 0, 0 ) ); + } + else if( cmd == "QRY" ) + { + // Do nothing + } + else if( cmd == "QNG" ) + { + //this is a reply from a ping + m_ping=false; + + // id is the timeout in fact, and we remove 5% of it + if( m_keepaliveTimer ) + m_keepaliveTimer->start( id * 950, true ); + kdDebug( 14140 ) << k_funcinfo << "timerTimeout=" << id << "sec"<< endl; + } + else if( cmd == "URL" ) + { + // URL 6 /cgi-bin/HoTMaiL https://loginnet.passport.com/ppsecure/md5auth.srf?lc=1033 2 + //example of reply: URL 10 /cgi-bin/HoTMaiL https://msnialogin.passport.com/ppsecure/md5auth.srf?lc=1036 3 + QString from_action_url = data.section( ' ', 1, 1 ); + QString rru = data.section( ' ', 0, 0 ); + QString id = data.section( ' ', 2, 2 ); + + //write the tmp file + QString UserID=m_account->accountId(); + + time_t actualTime; + time(&actualTime); + QString sl = QString::number( ( unsigned long ) actualTime - m_loginTime.toULong() ); + + QString md5this( m_MSPAuth + sl + m_password ); + KMD5 md5( md5this.utf8() ); + + QString hotmailRequest = "<html>\n" + "<head>\n" + "<noscript>\n" + "<meta http-equiv=Refresh content=\"0; url=http://www.hotmail.com\">\n" + "</noscript>\n" + "</head>\n" + "<body onload=\"document.pform.submit(); \">\n" + "<form name=\"pform\" action=\"" + from_action_url + "\" method=\"POST\">\n" + "<input type=\"hidden\" name=\"mode\" value=\"ttl\">\n" + "<input type=\"hidden\" name=\"login\" value=\"" + UserID.left( UserID.find('@') ) + "\">\n" + "<input type=\"hidden\" name=\"username\" value=\"" + UserID + "\">\n" + "<input type=\"hidden\" name=\"sid\" value=\"" + m_sid + "\">\n" + "<input type=\"hidden\" name=\"kv\" value=\"" + m_kv + "\">\n" + "<input type=\"hidden\" name=\"id\" value=\""+ id +"\">\n" + "<input type=\"hidden\" name=\"sl\" value=\"" + sl +"\">\n" + "<input type=\"hidden\" name=\"rru\" value=\"" + rru + "\">\n" + "<input type=\"hidden\" name=\"auth\" value=\"" + m_MSPAuth + "\">\n" + "<input type=\"hidden\" name=\"creds\" value=\"" + QString::fromLatin1( md5.hexDigest() ) + "\">\n" + "<input type=\"hidden\" name=\"svc\" value=\"mail\">\n" + "<input type=\"hidden\" name=\"js\" value=\"yes\">\n" + "</form></body>\n</html>\n"; + + KTempFile tmpMailFile( locateLocal( "tmp", "kopetehotmail-" ), ".html" ); + *tmpMailFile.textStream() << hotmailRequest; + tmpMailFile.file()->flush(); + + KRun::runURL( KURL::fromPathOrURL( tmpMailFile.name() ), "text/html" , true ); + + } + else if ( cmd == "NOT" ) + { + kdDebug( 14140 ) << k_funcinfo << "Received NOT command, issueing read block for '" << id << " more bytes" << endl; + readBlock( id ); + } + else + { + // Let the base class handle the rest + //MSNSocket::parseCommand( cmd, id, data ); + kdDebug( 14140 ) << k_funcinfo << "Unimplemented command '" << cmd << " " << id << " " << data << "' from server!" << endl; + } +} + + +void MSNNotifySocket::sslLoginFailed() +{ + m_disconnectReason=Kopete::Account::InvalidHost; + disconnect(); +} + +void MSNNotifySocket::sslLoginIncorrect() +{ + m_disconnectReason=Kopete::Account::BadPassword; + disconnect(); +} + +void MSNNotifySocket::sslLoginSucceeded(QString ticket) +{ + sendCommand("USR" , "TWN S " + ticket); + + m_secureLoginHandler->deleteLater(); + m_secureLoginHandler = 0L; +} + +void MSNNotifySocket::slotMSNAlertUnwanted() +{ + // user not interested .. clean up the list of actions + m_msnAlertURLs.clear(); +} + +void MSNNotifySocket::slotMSNAlertLink(unsigned int action) +{ + // index into our action list and pull out the URL that was clicked .. + KURL tempURLForLaunch(m_msnAlertURLs[action-1]); + + KRun* urlToRun = new KRun(tempURLForLaunch); +} + +void MSNNotifySocket::slotOpenInbox() +{ + sendCommand("URL", "INBOX" ); +} + +void MSNNotifySocket::sendMail(const QString &email) +{ + sendCommand("URL", QString("COMPOSE " + email).utf8() ); +} + +bool MSNNotifySocket::setUseHttpMethod(bool useHttp) +{ + bool ret = MSNSocket::setUseHttpMethod( useHttp ); + + if( useHttpMethod() ) { + if( m_keepaliveTimer ) { + delete m_keepaliveTimer; + m_keepaliveTimer = 0L; + } + } + else { + if( !m_keepaliveTimer ) { + m_keepaliveTimer = new QTimer( this, "m_keepaliveTimer" ); + QObject::connect( m_keepaliveTimer, SIGNAL( timeout() ), SLOT( slotSendKeepAlive() ) ); + } + } + + return ret; +} + +void MSNNotifySocket::slotReadMessage( const QByteArray &bytes ) +{ + QString msg = QString::fromUtf8(bytes, bytes.size()); + + if(msg.contains("text/x-msmsgsinitialmdatanotification")) + { + //Mail-Data: <MD><E><I>301</I><IU>1</IU><O>4</O><OU>2</OU></E><Q><QTM>409600</QTM><QNM>204800</QNM></Q></MD> + // MD - Mail Data + // E - email + // I - initial mail + // IU - initial unread + // O - other mail + // OU - other unread. + QRegExp regex("<MD><E><I>(\\d+)?</I>(?:<IU>(\\d+)?</IU>)<O>(\\d+)?</O><OU>(\\d+)?</OU></E><Q>.*</Q></MD>"); + regex.search(msg); + + bool unread; + // Retrieve the number of unread email messages. + mailCount = regex.cap(2).toUInt(&unread); + if(unread && mailCount > 0) + { + // If there are new email message available, raise the unread email event. + QObject::connect(KNotification::event( "msn_mail", i18n( "You have one unread message in your MSN inbox.", + "You have %n unread messages in your MSN inbox.", mailCount ), 0 , 0 , i18n( "Open Inbox..." ) ), + SIGNAL(activated(unsigned int ) ) , this, SLOT( slotOpenInbox() ) ); + } + } + else if(msg.contains("text/x-msmsgsactivemailnotification")) + { + //this sends the server if mails are deleted + QString m = msg.right(msg.length() - msg.find("Message-Delta:") ); + m = m.left(msg.find("\r\n")); + mailCount = mailCount - m.right(m.length() -m.find(" ")-1).toUInt(); + } + else if(msg.contains("text/x-msmsgsemailnotification")) + { + //this sends the server if a new mail has arrived + QRegExp rx("From-Addr: ([A-Za-z0-9@._\\-]*)"); + rx.search(msg); + QString m=rx.cap(1); + + mailCount++; + + //TODO: it is also possible to get the subject (but warning about the encoding) + QObject::connect(KNotification::event( "msn_mail",i18n( "You have one new email from %1 in your MSN inbox." ).arg(m), + 0 , 0 , i18n( "Open Inbox..." ) ), + SIGNAL(activated(unsigned int ) ) , this, SLOT( slotOpenInbox() ) ); + } + else if(msg.contains("text/x-msmsgsprofile")) + { + //Hotmail profile + if(msg.contains("MSPAuth:")) + { + QRegExp rx("MSPAuth: ([A-Za-z0-9$!*]*)"); + rx.search(msg); + m_MSPAuth=rx.cap(1); + } + if(msg.contains("sid:")) + { + QRegExp rx("sid: ([0-9]*)"); + rx.search(msg); + m_sid=rx.cap(1); + } + if(msg.contains("kv:")) + { + QRegExp rx("kv: ([0-9]*)"); + rx.search(msg); + m_kv=rx.cap(1); + } + if(msg.contains("LoginTime:")) + { + QRegExp rx("LoginTime: ([0-9]*)"); + rx.search(msg); + m_loginTime=rx.cap(1); + } + else //IN MSNP9 there are no logintime it seems, so set it manualy + { + time_t actualTime; + time(&actualTime); + m_loginTime=QString::number((unsigned long)actualTime); + } + if(msg.contains("EmailEnabled:")) + { + QRegExp rx("EmailEnabled: ([0-9]*)"); + rx.search(msg); + m_isHotmailAccount = (rx.cap(1).toUInt() == 1); + emit hotmailSeted(m_isHotmailAccount); + } + if(msg.contains("ClientIP:")) + { + QRegExp rx("ClientIP: ([0-9.]*)"); + rx.search(msg); + m_localIP = rx.cap(1); + } + + // We are logged when we receive the initial profile from Hotmail. + m_isLogged = true; + } + else if (msg.contains("NOTIFICATION")) + { + // MSN alert (i.e. NOTIFICATION) [for docs see http://www.hypothetic.org/docs/msn/client/notification.php] + // format of msg is as follows: + // + // <NOTIFICATION ver="2" id="1342902633" siteid="199999999" siteurl="http://alerts.msn.com"> + // <TO pid="0x0006BFFD:0x8582C0FB" name="example@passport.com"/> + // <MSG pri="1" id="1342902633"> + // <SUBSCR url="http://g.msn.com/3ALMSNTRACKING/199999999ToastChange?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/> + // <ACTION url="http://g.msn.com/3ALMSNTRACKING/199999999ToastAction?http://alerts.msn.com/Alerts/MyAlerts.aspx?strela=1"/> + // <BODY lang="3076" icon=""> + // <TEXT>utf8-encoded text</TEXT> + // </BODY> + // </MSG> + // </NOTIFICATION> + + // MSN sends out badly formed XML .. fix it for them (thanks MS!) + QString notificationDOMAsString(msg); + + QRegExp rx( "&(?!amp;)" ); // match ampersands but not & + notificationDOMAsString.replace(rx, "&"); + QDomDocument alertDOM; + alertDOM.setContent(notificationDOMAsString); + + QDomNodeList msgElements = alertDOM.elementsByTagName("MSG"); + for (uint i = 0 ; i < msgElements.count() ; i++) + { + QString subscString; + QString actionString; + QString textString; + + QDomNode msgDOM = msgElements.item(i); + + QDomNodeList msgChildren = msgDOM.childNodes(); + for (uint i = 0 ; i < msgChildren.length() ; i++) { + QDomNode child = msgChildren.item(i); + QDomElement element = child.toElement(); + if (element.tagName() == "SUBSCR") + { + QDomAttr subscElementURLAttribute; + if (element.hasAttribute("url")) + { + subscElementURLAttribute = element.attributeNode("url"); + subscString = subscElementURLAttribute.value(); + } + } + else if (element.tagName() == "ACTION") + { + // process ACTION node to pull out URL the alert is tied to + QDomAttr actionElementURLAttribute; + if (element.hasAttribute("url")) + { + actionElementURLAttribute = element.attributeNode("url"); + actionString = actionElementURLAttribute.value(); + } + } + else if (element.tagName() == "BODY") + { + // process BODY node to get the text of the alert + QDomNodeList textElements = element.elementsByTagName("TEXT"); + if (textElements.count() >= 1) + { + QDomElement textElement = textElements.item(0).toElement(); + textString = textElement.text(); + } + } + + + } + +// kdDebug( 14140 ) << "subscString " << subscString << " actionString " << actionString << " textString " << textString << endl; + // build an internal list of actions ... we'll need to index into this list when we receive an event + QStringList actions; + actions.append(i18n("More Information")); + m_msnAlertURLs.append(actionString); + + actions.append(i18n("Manage Subscription")); + m_msnAlertURLs.append(subscString); + + // Don't do any MSN alerts notification for new blog updates + if( subscString != QString::fromLatin1("s.htm") && actionString != QString::fromLatin1("a.htm") ) + { + KNotification* notification = KNotification::event("msn_alert", textString, 0L, 0L, actions); + QObject::connect(notification, SIGNAL(activated(unsigned int)), this, SLOT(slotMSNAlertLink(unsigned int))); + QObject::connect(notification, SIGNAL(closed()), this, SLOT(slotMSNAlertUnwanted())); + } + } // end for each MSG tag + } + + if(!m_configFile.isNull()) + { + // TODO Get client features. + } + + if(!m_tmpLastHandle.isNull()) + { + QString personalMessage, currentMedia; + QDomDocument psm; + if( psm.setContent(msg) ) + { + // Get the first child of the xml "document"; + QDomElement psmElement = psm.documentElement().firstChild().toElement(); + + while( !psmElement.isNull() ) + { + if(psmElement.tagName() == QString::fromUtf8("PSM")) + { + personalMessage = psmElement.text(); + kdDebug(14140) << k_funcinfo << "Personnal Message received: " << personalMessage << endl; + } + else if(psmElement.tagName() == QString::fromUtf8("CurrentMedia")) + { + if( !psmElement.text().isEmpty() ) + { + kdDebug(14140) << k_funcinfo << "XML CurrentMedia: " << psmElement.text() << endl; + currentMedia = processCurrentMedia( psmElement.text() ); + } + } + psmElement = psmElement.nextSibling().toElement(); + } + + MSNContact *contact = static_cast<MSNContact*>(m_account->contacts()[ m_tmpLastHandle ]); + if(contact) + { + contact->setProperty(MSNProtocol::protocol()->propPersonalMessage, currentMedia.isEmpty() ? personalMessage : currentMedia); + } + } + m_tmpLastHandle = QString::null; + } +} + +QString MSNNotifySocket::processCurrentMedia( const QString &mediaXmlElement ) +{ + /* + The value of the CurrentMedia tag you can think of like an array + seperated by "\0" characters (literal backslash followed by zero, not NULL). + + The elements of this "array" are as follows: + + * Application - This is the app you are using. Usually empty + * Type - This is the type of PSM, either “Music”, “Games” or “Office” + * Enabled - This is a boolean value (0/1) to enable/disable + * Format - A formatter string ala .Net; For example, “{0} - {1}” + * First line - The first line (Matches {0} in the Format) + * Second line - The second line (Matches {1} in the Format) + * Third line - The third line (Matches {2} in the Format) + + There is probably no limit to the number of lines unless you go over the maximum length of the tag. + + Example of currentMedia xml tag: + <CurrentMedia>\0Music\01\0{0} - {1}\0 Song Title\0Song Artist\0Song Album\0\0</CurrentMedia> + <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia> + <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia> + + From http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes + */ + QString application, type, format, currentMedia; + bool enabled=false, test; + // \0 is textual, it's the "array" separator. + QStringList argumentLists = QStringList::split(QString::fromUtf8("\\0"), mediaXmlElement, true); + + // Retrive the "stable" array elements. + application = argumentLists[0]; + type = argumentLists[1]; + enabled = argumentLists[2].toInt(&test); + format = argumentLists[3]; + + // Get the formatter strings + QStringList formatterStrings; + QStringList::ConstIterator it; + for( it = argumentLists.at(4); it != argumentLists.end(); ++it ) + { + formatterStrings.append( *it ); + } + + // Replace the formatter in the format string. + currentMedia = format; + for(uint i=0; i<formatterStrings.size(); i++) + { + currentMedia = currentMedia.replace(QString("{%1}").arg(i), formatterStrings[i]); + } + + if( type == QString::fromUtf8("Music") ) + { + // the "♫" is encoded in utf8 (and should be in utf8) + currentMedia = i18n("Now Listening: ♫ %1 ♫").arg(currentMedia); + } + + kdDebug(1414) << "Current Media received: " << currentMedia << endl; + + return currentMedia; +} + +void MSNNotifySocket::addGroup(const QString& groupName) +{ + // escape spaces + sendCommand( "ADG", escape( groupName ) ); +} + +void MSNNotifySocket::renameGroup( const QString& groupName, const QString& groupGuid ) +{ + // escape spaces + sendCommand( "REG", groupGuid + " " + escape( groupName ) ); +} + +void MSNNotifySocket::removeGroup( const QString& groupGuid ) +{ + sendCommand( "RMG", groupGuid ); +} + +void MSNNotifySocket::addContact( const QString &handle, int list, const QString& publicName, const QString& contactGuid, const QString& groupGuid ) +{ + QString args; + switch( list ) + { + case MSNProtocol::FL: + { + // Adding the contact to a group + if( !contactGuid.isEmpty() ) + { + args = QString("FL C=%1 %2").arg( contactGuid ).arg( groupGuid ); + kdDebug(14140) << k_funcinfo << "In adding contact to a group" << endl; + } + // Adding a new contact + else + { + args = QString("FL N=%1 F=%2").arg( handle ).arg( escape( publicName ) ); + kdDebug(14140) << k_funcinfo << "In adding contact to a new contact" << endl; + } + break; + } + case MSNProtocol::AL: + args = QString("AL N=%1").arg( handle ); + break; + case MSNProtocol::BL: + args = QString("BL N=%1").arg( handle ); + break; + case MSNProtocol::RL: + args = QString("RL N=%1").arg( handle ); + break; + default: + kdDebug(14140) << k_funcinfo <<"WARNING! Unknown list " << list << "!" << endl; + return; + } + unsigned int id=sendCommand( "ADC", args ); + m_tmpHandles[id]=handle; +} + +void MSNNotifySocket::removeContact( const QString &handle, int list, const QString& contactGuid, const QString& groupGuid ) +{ + QString args; + switch( list ) + { + case MSNProtocol::FL: + args = "FL " + contactGuid; + // Removing a contact from a group + if( !groupGuid.isEmpty() ) + args += " " + groupGuid; + break; + case MSNProtocol::AL: + args = "AL " + handle; + break; + case MSNProtocol::BL: + args = "BL " + handle; + break; + case MSNProtocol::PL: + args = "PL " + handle; + break; + default: + kdDebug(14140) <<k_funcinfo << "WARNING! Unknown list " << list << "!" << endl; + return; + } + unsigned int id=sendCommand( "REM", args ); + m_tmpHandles[id]=handle; +} + +void MSNNotifySocket::setStatus( const Kopete::OnlineStatus &status ) +{ +// kdDebug( 14140 ) << k_funcinfo << statusToString( status ) << endl; + + if( onlineStatus() == Disconnected ) + m_newstatus = status; + else + sendCommand( "CHG", statusToString( status ) + " " + m_account->myselfClientId() + " " + escape(m_account->pictureObject()) ); +} + +void MSNNotifySocket::changePublicName( const QString &publicName, const QString &handle ) +{ + QString tempPublicName = publicName; + + //The maximum length is 387. but with utf8 or encodage, each character may be triple + // 387/3 = 129 so we make sure the lenght is not logner than 129 char, even if + // it's possible to have longer nicks. + if( escape(publicName).length() > 129 ) + { + tempPublicName = publicName.left(129); + } + + if( handle.isNull() ) + { + unsigned int id = sendCommand( "PRP", "MFN " + escape( tempPublicName ) ); + m_tmpHandles[id] = m_account->accountId(); + } + else + { + MSNContact *currentContact = static_cast<MSNContact *>(m_account->contacts()[handle]); + if(currentContact && !currentContact->guid().isEmpty() ) + { + // FIXME if there is not guid server disconnects. + unsigned int id = sendCommand( "SBP", currentContact->guid() + " MFN " + escape( tempPublicName ) ); + m_tmpHandles[id] = handle; + } + } +} + +void MSNNotifySocket::changePersonalMessage( MSNProtocol::PersonalMessageType type, const QString &personalMessage ) +{ + QString tempPersonalMessage; + QString xmlCurrentMedia; + + // Only espace and cut the personalMessage is the type is normal. + if(type == MSNProtocol::PersonalMessageNormal) + { + tempPersonalMessage = personalMessage; + //Magic number : 129 characters + if( escape(personalMessage).length() > 129 ) + { + // We cut. for now. + tempPersonalMessage = personalMessage.left(129); + } + } + + QDomDocument xmlMessage; + xmlMessage.appendChild( xmlMessage.createElement( "Data" ) ); + + QDomElement psm = xmlMessage.createElement("PSM"); + psm.appendChild( xmlMessage.createTextNode( tempPersonalMessage ) ); + xmlMessage.documentElement().appendChild( psm ); + + QDomElement currentMedia = xmlMessage.createElement("CurrentMedia"); + + /* Example of currentMedia xml tag: + <CurrentMedia>\0Music\01\0{0} - {1}\0 Song Title\0Song Artist\0Song Album\0\0</CurrentMedia> + <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia> + <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia> + */ + switch(type) + { + case MSNProtocol::PersonalMessageMusic: + { + xmlCurrentMedia = "\\0Music\\01\\0"; + QStringList mediaList = QStringList::split(";", personalMessage); + QString formatterArguments; + if( !mediaList[0].isEmpty() ) // Current Track + { + xmlCurrentMedia += "{0}"; + formatterArguments += QString("%1\\0").arg(mediaList[0]); + } + if( !mediaList[1].isEmpty() ) // Current Artist + { + xmlCurrentMedia += " - {1}"; + formatterArguments += QString("%1\\0").arg(mediaList[1]); + } + if( !mediaList[2].isEmpty() ) // Current Album + { + xmlCurrentMedia += " ({2})"; + formatterArguments += QString("%1\\0").arg(mediaList[2]); + } + xmlCurrentMedia += "\\0" + formatterArguments + "\\0"; + break; + } + default: + break; + } + + currentMedia.appendChild( xmlMessage.createTextNode( xmlCurrentMedia ) ); + + // Set the status message for myself, check if currentMedia is empty, for either using the normal or Music personal + m_propertyPersonalMessage = xmlCurrentMedia.isEmpty() ? tempPersonalMessage : processCurrentMedia( currentMedia.text() ); + + xmlMessage.documentElement().appendChild( currentMedia ); + + unsigned int id = sendCommand("UUX","",true, xmlMessage.toString().utf8(), false); + m_tmpHandles[id] = m_account->accountId(); + +} + +void MSNNotifySocket::changePhoneNumber( const QString &key, const QString &data ) +{ + sendCommand( "PRP", key + " " + escape ( data ) ); +} + + +void MSNNotifySocket::createChatSession() +{ + sendCommand( "XFR", "SB" ); +} + +QString MSNNotifySocket::statusToString( const Kopete::OnlineStatus &status ) const +{ + if( status == MSNProtocol::protocol()->NLN ) + return "NLN"; + else if( status == MSNProtocol::protocol()->BSY ) + return "BSY"; + else if( status == MSNProtocol::protocol()->BRB ) + return "BRB"; + else if( status == MSNProtocol::protocol()->AWY ) + return "AWY"; + else if( status == MSNProtocol::protocol()->PHN ) + return "PHN"; + else if( status == MSNProtocol::protocol()->LUN ) + return "LUN"; + else if( status == MSNProtocol::protocol()->FLN ) + return "FLN"; + else if( status == MSNProtocol::protocol()->HDN ) + return "HDN"; + else if( status == MSNProtocol::protocol()->IDL ) + return "IDL"; + else + { + kdWarning( 14140 ) << k_funcinfo << "Unknown status " << status.internalStatus() << "!" << endl; + return "UNK"; + } +} + +void MSNNotifySocket::slotSendKeepAlive() +{ + //we did not received the previous QNG + if(m_ping) + { + m_disconnectReason=Kopete::Account::ConnectionReset; + disconnect(); + /*KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Information, + i18n( "The connection with the MSN network has been lost." ) , i18n ("MSN Plugin") );*/ + return; + } + else + { + // Send a dummy command to fake activity. This makes sure MSN doesn't + // disconnect you when the notify socket is idle. + sendCommand( "PNG" , QString::null , false ); + m_ping=true; + } + + //at least 90 second has been ellapsed since the last messages + // we shouldn't receive error from theses command anymore + m_tmpHandles.clear(); +} + +Kopete::OnlineStatus MSNNotifySocket::convertOnlineStatus( const QString &status ) +{ + if( status == "NLN" ) + return MSNProtocol::protocol()->NLN; + else if( status == "FLN" ) + return MSNProtocol::protocol()->FLN; + else if( status == "HDN" ) + return MSNProtocol::protocol()->HDN; + else if( status == "PHN" ) + return MSNProtocol::protocol()->PHN; + else if( status == "LUN" ) + return MSNProtocol::protocol()->LUN; + else if( status == "BRB" ) + return MSNProtocol::protocol()->BRB; + else if( status == "AWY" ) + return MSNProtocol::protocol()->AWY; + else if( status == "BSY" ) + return MSNProtocol::protocol()->BSY; + else if( status == "IDL" ) + return MSNProtocol::protocol()->IDL; + else + return MSNProtocol::protocol()->UNK; +} + + +#include "msnnotifysocket.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnnotifysocket.h b/kopete/protocols/msn/msnnotifysocket.h new file mode 100644 index 00000000..7f915410 --- /dev/null +++ b/kopete/protocols/msn/msnnotifysocket.h @@ -0,0 +1,216 @@ +/* + msnnotifysocket.h - Notify Socket for the MSN Protocol + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + Portions taken from + KMerlin (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNNOTIFYSOCKET_H +#define MSNNOTIFYSOCKET_H + +#include "msnsocket.h" +#include "msnprotocol.h" + + +class MSNDispatchSocket; +class MSNAccount; +class KTempFile; +class MSNSecureLoginHandler; +class MSNChallengeHandler; + +/** + * @author Olaf Lueg + * @author Olivier Goffart + */ +class MSNNotifySocket : public MSNSocket +{ + Q_OBJECT + +public: + MSNNotifySocket( MSNAccount* account, const QString &msnId, const QString &password ); + ~MSNNotifySocket(); + + virtual void disconnect(); + + void setStatus( const Kopete::OnlineStatus &status ); + void addContact( const QString &handle, int list, const QString& publicName, const QString& contactGuid, const QString& groupGuid ); + void removeContact( const QString &handle, int list, const QString &contactGuid, const QString &groupGuid ); + + void addGroup( const QString& groupName ); + void removeGroup( const QString& group ); + void renameGroup( const QString& groupName, const QString& groupGuid ); + + void changePublicName( const QString& publicName , const QString &handle=QString::null ); + void changePersonalMessage( MSNProtocol::PersonalMessageType type , const QString& personalMessage ); + + void changePhoneNumber( const QString &key, const QString &data ); + + void createChatSession(); + + void sendMail(const QString &email); + + /** + * this should return a Kopete::Account::DisconnectReason value + */ + int disconnectReason() { return m_disconnectReason; } + + QString localIP() { return m_localIP; } + + bool setUseHttpMethod( bool useHttpMethod ); + + bool isLogged() const { return m_isLogged; } + +public slots: + void slotOpenInbox(); + void slotMSNAlertLink(unsigned int action); + void slotMSNAlertUnwanted(); + +signals: + void newContactList(); + void contactList(const QString& handle, const QString& publicName, const QString &contactGuid, uint lists, const QString& groups); + void contactStatus(const QString&, const QString&, const QString& ); + void contactAdded(const QString& handle, const QString& list, const QString& publicName, const QString& contactGuid, const QString& groupGuid); + //void contactRemoved(const QString&, const QString&, uint); + void contactRemoved(const QString& handle, const QString& list, const QString& contactGuid, const QString& groupGuid); + + void groupListed(const QString&, const QString&); + void groupAdded( const QString&, const QString&); + void groupRenamed( const QString&, const QString& ); + void groupRemoved( const QString& ); + + void invitedToChat(const QString&, const QString&, const QString&, const QString&, const QString& ); + void startChat( const QString&, const QString& ); + + void statusChanged( const Kopete::OnlineStatus &newStatus ); + + void hotmailSeted(bool) ; + + + /** + * When the dispatch server sends us the notification server to use, this + * signal is emitted. After this the socket is automatically closed. + */ + void receivedNotificationServer( const QString &host, uint port ); + + +protected: + /** + * Handle an MSN command response line. + */ + virtual void parseCommand( const QString &cmd, uint id, + const QString &data ); + + /** + * Handle an MSN error condition. + * This reimplementation handles most of the other MSN error codes. + */ + virtual void handleError( uint code, uint id ); + + /** + * This reimplementation sets up the negotiating with the server and + * suppresses the change of the status to online until the handshake + * is complete. + */ + virtual void doneConnect(); + + +private slots: + /** + * We received a message from the server, which is sent as raw data, + * instead of cr/lf line-based text. + */ + void slotReadMessage( const QByteArray &bytes ); + + /** + * Send a keepalive to the server to avoid idle connections to cause + * MSN closing the connection + */ + void slotSendKeepAlive(); + + void sslLoginFailed(); + void sslLoginIncorrect(); + void sslLoginSucceeded(QString ticket); + + +private: + /** + * Convert the MSN status strings to a Kopete::OnlineStatus + */ + Kopete::OnlineStatus convertOnlineStatus( const QString &statusString ); + + MSNAccount *m_account; + QString m_password; + QStringList m_msnAlertURLs; + + unsigned int mailCount; + + Kopete::OnlineStatus m_newstatus; + + /** + * Convert an entry of the Status enum back to a string + */ + QString statusToString( const Kopete::OnlineStatus &status ) const; + + /** + * Process the CurrentMedia XML element. + * @param mediaXmlElement the source XML element as text. + */ + QString processCurrentMedia( const QString &mediaXmlElement ); + + //know the last handle used + QString m_tmpLastHandle; + QMap <unsigned int,QString> m_tmpHandles; + QString m_configFile; + + //for hotmail inbox opening + bool m_isHotmailAccount; + QString m_MSPAuth; + QString m_kv; + QString m_sid; + QString m_loginTime; + QString m_localIP; + MSNSecureLoginHandler *m_secureLoginHandler; + + MSNChallengeHandler *m_challengeHandler; + QTimer *m_keepaliveTimer; + + bool m_ping; + + int m_disconnectReason; + + /** + * Used to set the myself() personalMessage when the acknowledge(UUX) command is received. + * The personalMessage is built into @ref changePersonalMessage + */ + QString m_propertyPersonalMessage; + + /** + * Used to tell when we are logged in to MSN Messeger service. + * Logged when we receive the initial profile message from Hotmail. + * + * Some commands only make sense to be done when logged. + */ + bool m_isLogged; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnprotocol.cpp b/kopete/protocols/msn/msnprotocol.cpp new file mode 100644 index 00000000..2a5b4319 --- /dev/null +++ b/kopete/protocols/msn/msnprotocol.cpp @@ -0,0 +1,179 @@ +/* + msnprotocol.cpp - Kopete MSN Protocol Plugin + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include <qimage.h> + +#include <kdebug.h> +#include <kgenericfactory.h> +#include <kconfig.h> +#include <kdeversion.h> +#include <kaboutdata.h> + +#include "kopeteaccountmanager.h" +#include "kopeteglobal.h" +#include "kopeteonlinestatusmanager.h" + +#include "msnaddcontactpage.h" +#include "msneditaccountwidget.h" +#include "msncontact.h" +#include "msnaccount.h" +#include "msnprotocol.h" +#include "msnchatsession.h" + +typedef KGenericFactory<MSNProtocol> MSNProtocolFactory; +#if KDE_IS_VERSION(3,2,90) +static const KAboutData aboutdata("kopete_msn", I18N_NOOP("MSN Messenger") , "1.0" ); +K_EXPORT_COMPONENT_FACTORY( libkopete_msn_shared, MSNProtocolFactory( &aboutdata ) ) +#else +K_EXPORT_COMPONENT_FACTORY( libkopete_msn_shared, MSNProtocolFactory( "kopete_msn" ) ) +#endif + +MSNProtocol *MSNProtocol::s_protocol = 0L; + +MSNProtocol::MSNProtocol( QObject *parent, const char *name, const QStringList & /* args */ ) +: Kopete::Protocol( MSNProtocolFactory::instance(), parent, name ), + NLN( Kopete::OnlineStatus::Online, 25, this, 1, QString::null, i18n( "Online" ) , i18n( "O&nline" ), Kopete::OnlineStatusManager::Online,Kopete::OnlineStatusManager::HasAwayMessage ), + BSY( Kopete::OnlineStatus::Away, 20, this, 2, "msn_busy", i18n( "Busy" ) , i18n( "&Busy" ), Kopete::OnlineStatusManager::Busy, Kopete::OnlineStatusManager::HasAwayMessage ), + BRB( Kopete::OnlineStatus::Away, 22, this, 3, "msn_brb", i18n( "Be Right Back" ), i18n( "Be &Right Back" ) , 0 , Kopete::OnlineStatusManager::HasAwayMessage ), + AWY( Kopete::OnlineStatus::Away, 18, this, 4, "contact_away_overlay", i18n( "Away From Computer" ),i18n( "&Away" ), Kopete::OnlineStatusManager::Away, Kopete::OnlineStatusManager::HasAwayMessage ), + PHN( Kopete::OnlineStatus::Away, 12, this, 5, "contact_phone_overlay", i18n( "On the Phone" ) , i18n( "On The &Phone" ) , 0 , Kopete::OnlineStatusManager::HasAwayMessage ), + LUN( Kopete::OnlineStatus::Away, 15, this, 6, "contact_food_overlay", i18n( "Out to Lunch" ) , i18n( "Out To &Lunch" ) , 0 , Kopete::OnlineStatusManager::HasAwayMessage ), + FLN( Kopete::OnlineStatus::Offline, 0, this, 7, QString::null, i18n( "Offline" ) , i18n( "&Offline" ), Kopete::OnlineStatusManager::Offline,Kopete::OnlineStatusManager::DisabledIfOffline ), + HDN( Kopete::OnlineStatus::Invisible, 3, this, 8, "contact_invisible_overlay", i18n( "Invisible" ) , i18n( "&Invisible" ), Kopete::OnlineStatusManager::Invisible ), + IDL( Kopete::OnlineStatus::Away, 10, this, 9, "contact_away_overlay", i18n( "Idle" ) , i18n( "&Idle" ), Kopete::OnlineStatusManager::Idle , Kopete::OnlineStatusManager::HideFromMenu ), + UNK( Kopete::OnlineStatus::Unknown, 25, this, 0, "status_unknown", i18n( "Status not available" ) ), + CNT( Kopete::OnlineStatus::Connecting, 2, this, 10,"msn_connecting", i18n( "Connecting" ) ), + propEmail(Kopete::Global::Properties::self()->emailAddress()), + propPhoneHome(Kopete::Global::Properties::self()->privatePhone()), + propPhoneWork(Kopete::Global::Properties::self()->workPhone()), + propPhoneMobile(Kopete::Global::Properties::self()->privateMobilePhone()), + propClient("client", i18n("Remote Client"), 0, false), + propGuid("guid", i18n("Contact GUID"), 0, true), + propPersonalMessage(Kopete::Global::Properties::self()->awayMessage()) +{ + s_protocol = this; + + addAddressBookField( "messaging/msn", Kopete::Plugin::MakeIndexField ); + + setCapabilities( Kopete::Protocol::BaseFgColor | Kopete::Protocol::BaseFont | Kopete::Protocol::BaseFormatting ); + + // m_status = m_unknownStatus = UNK; +} + +Kopete::Contact *MSNProtocol::deserializeContact( Kopete::MetaContact *metaContact, const QMap<QString, QString> &serializedData, + const QMap<QString, QString> & /* addressBookData */ ) +{ + QString contactId = serializedData[ "contactId" ] ; + QString accountId = serializedData[ "accountId" ] ; + QString lists = serializedData[ "lists" ]; + QStringList groups = QStringList::split( ",", serializedData[ "groups" ] ); + QString contactGuid = serializedData[ "contactGuid" ] ; + + QDict<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts( this ); + + Kopete::Account *account = accounts[ accountId ]; + if( !account ) + account = createNewAccount( accountId ); + + // Create MSN contact + MSNContact *c = new MSNContact( account, contactId, metaContact ); + + for( QStringList::Iterator it = groups.begin() ; it != groups.end(); ++it ) + c->contactAddedToGroup( *it, 0L /* FIXME - m_groupList[ ( *it ).toUInt() ]*/ ); + + c->m_obj= serializedData[ "obj" ]; + c->setInfo( "PHH" , serializedData[ "PHH" ] ); + c->setInfo( "PHW" , serializedData[ "PHW" ] ); + c->setInfo( "PHM" , serializedData[ "PHM" ] ); + c->setProperty( propGuid, contactGuid ); + + c->setBlocked( (bool)(lists.contains('B')) ); + c->setAllowed( (bool)(lists.contains('A')) ); + c->setReversed( (bool)(lists.contains('R')) ); + + return c; +} + +AddContactPage *MSNProtocol::createAddContactWidget(QWidget *parent , Kopete::Account *i) +{ + return (new MSNAddContactPage(i->isConnected(),parent)); +} + +KopeteEditAccountWidget *MSNProtocol::createEditAccountWidget(Kopete::Account *account, QWidget *parent) +{ + return new MSNEditAccountWidget(this,account,parent); +} + +Kopete::Account *MSNProtocol::createNewAccount(const QString &accountId) +{ + return new MSNAccount(this, accountId); +} + + +// NOTE: CALL THIS ONLY BEING CONNECTED +void MSNProtocol::slotSyncContactList() +{ +/* if ( ! mIsConnected ) + { + return; + } + // First, delete D marked contacts + QStringList localcontacts; + + contactsFile->setGroup("Default"); + + contactsFile->readListEntry("Contacts",localcontacts); + QString tmpUin; + tmpUin.sprintf("%d",uin); + tmp.append(tmpUin); + cnt=contactsFile->readNumEntry("Count",0); +*/ +} + +MSNProtocol* MSNProtocol::protocol() +{ + return s_protocol; +} + +bool MSNProtocol::validContactId(const QString& userid) +{ + return ( userid.contains('@') ==1 && userid.contains('.') >=1 && userid.contains(' ') == 0); +} + +QImage MSNProtocol::scalePicture(const QImage &picture) +{ + QImage img(picture); + img = img.smoothScale( 96, 96, QImage::ScaleMin ); + // crop image if not square + if(img.width() < img.height()) + { + img = img.copy((img.width()-img.height())/2, 0, 96, 96); + } + else if(img.width() > img.height()) + { + img = img.copy(0, (img.height()-img.width())/2, 96, 96); + } + + return img; +} +#include "msnprotocol.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnprotocol.h b/kopete/protocols/msn/msnprotocol.h new file mode 100644 index 00000000..7017fd90 --- /dev/null +++ b/kopete/protocols/msn/msnprotocol.h @@ -0,0 +1,187 @@ +/* + msnprotocol.h - Kopete MSN Protocol Plugin + + Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org> + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef __msnprotocol_h__ +#define __msnprotocol_h__ + +#include <qmap.h> +#include <qstringlist.h> + +#include "kopeteprotocol.h" +#include "kopeteonlinestatus.h" +#include "kopetecontactproperty.h" + +#include "msnsocket.h" + + +class QImage; + +class KAction; +class KActionMenu; + +class MSNContact; +class MSNAccount; +class MSNNotifySocket; +class MSNSwitchBoardSocket; +class MSNChatSession; +class MSNInvitation; +namespace Kopete { class ChatSession; } +namespace Kopete { class MetaContact; } +namespace Kopete { class Contact; } +namespace Kopete { class Message; } +namespace Kopete { class Group; } + +/** + * @author duncan + * @author Martijn Klingens <klingens@kde.org> + * @author Olivier Goffart <ogoffart @ kde.org> + */ +class KOPETE_EXPORT MSNProtocol : public Kopete::Protocol +{ + Q_OBJECT + +public: + MSNProtocol( QObject *parent, const char *name, const QStringList &args ); + + /** + * SyncMode indicates whether settings differing between client and + * server should be propagated to keep them in sync. + * SyncToServer - Ignore the server setting when sent. Instead, push + * the local setting to the server. Used when changing + * settings offline. + * SyncFromServer - Update locally stored settings with the value sent + * by the server. Used when connecting to the server if + * no offline changes are pending to force a sync. + * SyncBoth - Changes are updated both ways. This is truly a + * 'first come, first serve' scenario, which breaks if + * the 'old' value is sent by one peer before the other + * end is able to push the new value. An example of this + * is changing the MSN nickname offline - the server can + * only be updated after it has sent the old value to + * the client during connect, destroying the new setting. + * Once connected this is often the most useful setting. + * DontSync - Do not sync values at all. This is used if settings + * are overridden locally, but should not be sent to the + * server, nor should the client update server-pushed + * values. This can be useful for e.g. contact lists. + */ + enum SyncMode + { + DontSync = 0x00, + SyncToServer = 0x01, + SyncFromServer = 0x02, + SyncBoth = 0x03 + }; + + /** + * The possible MSN online statuses + */ + const Kopete::OnlineStatus NLN; //online + const Kopete::OnlineStatus BSY; //busy + const Kopete::OnlineStatus BRB; //be right back + const Kopete::OnlineStatus AWY; //away + const Kopete::OnlineStatus PHN; //on the phone + const Kopete::OnlineStatus LUN; //out to lunch + const Kopete::OnlineStatus FLN; //offline + const Kopete::OnlineStatus HDN; //invisible + const Kopete::OnlineStatus IDL; //idle + const Kopete::OnlineStatus UNK; //inknown (internal) + const Kopete::OnlineStatus CNT; //connecting (internal) + + const Kopete::ContactPropertyTmpl propEmail; + const Kopete::ContactPropertyTmpl propPhoneHome; + const Kopete::ContactPropertyTmpl propPhoneWork; + const Kopete::ContactPropertyTmpl propPhoneMobile; + const Kopete::ContactPropertyTmpl propClient; + const Kopete::ContactPropertyTmpl propGuid; + const Kopete::ContactPropertyTmpl propPersonalMessage; // it's the equivalent of away message. + + enum List + { + FL, // forward + AL, // allow + BL, // blocked + RL, // reverse + PL // pending + }; + + // Enums used to build the Kopete's MSN ClientId. + enum MSNClientInformationFields + { + WindowsMobile = 0x1, + InkFormatGIF = 0x04, + InkFormatISF = 0x08, + SupportWebcam = 0x10, + SupportMultiPacketMessaging = 0x20, + MSNMobileDevice = 0x40, + MSNDirectDevice = 0x80, + WebMessenger = 0x100, + SupportDirectIM = 0x4000, + SupportWinks = 0x8000, + MSNC1 = 0x10000000, + MSNC2 = 0x20000000, + MSNC3 = 0x30000000, + MSNC4 = 0x40000000 + }; + + enum PersonalMessageType + { + PersonalMessageNormal, + PersonalMessageMusic, + PersonalMessageGame, + PersonalMessageOffice + }; + + virtual Kopete::Contact *deserializeContact( Kopete::MetaContact *metaContact, + const QMap<QString, QString> &serializedData, const QMap<QString, QString> &addressBookData ); + + virtual AddContactPage *createAddContactWidget( QWidget *parent , Kopete::Account *i); + virtual KopeteEditAccountWidget *createEditAccountWidget(Kopete::Account *account, QWidget *parent); + virtual Kopete::Account *createNewAccount(const QString &accountId); + + static MSNProtocol* protocol(); + static bool validContactId(const QString&); + QImage scalePicture(const QImage &picture); + +private slots: + void slotSyncContactList(); + +private: + + static MSNProtocol *s_protocol; + +signals: + /** + * A new msn invitation has been arrived. plugins can connect this signal to handle invitations. + * if the invitationID match to their internal id. they can create a new MSNInvitation and pass it via invitation + * + * @param invitation should be set by the plugin to the new invitaiton. plugin should check it is equal to 0L before + * @param bodyMSG is the whole invitation message + * @param cookie is the invitation cookie + * @param msnMM is the message manager + * @param c is the contact + */ + void invitation(MSNInvitation*& invitation, const QString &bodyMSG , long unsigned int cookie , MSNChatSession* msnMM , MSNContact* c ); +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnsecureloginhandler.cpp b/kopete/protocols/msn/msnsecureloginhandler.cpp new file mode 100644 index 00000000..00f862fe --- /dev/null +++ b/kopete/protocols/msn/msnsecureloginhandler.cpp @@ -0,0 +1,131 @@ +/* + msnsecureloginhandler.cpp - SSL login for MSN protocol + + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#include "msnsecureloginhandler.h" + +// Qt includes +#include <qregexp.h> + +// KDE includes +#include <kio/job.h> +#include <kurl.h> +#include <kdebug.h> + +MSNSecureLoginHandler::MSNSecureLoginHandler(const QString &accountId, const QString &password, const QString &authParameters) + : m_password(password), m_accountId(accountId), m_authentification(authParameters) +{ + +} + +MSNSecureLoginHandler::~MSNSecureLoginHandler() +{ +// kdDebug(14140) << k_funcinfo << endl; +} + +void MSNSecureLoginHandler::login() +{ + // Retrive the login server. + // Do a reload and don't show the progress. + KIO::Job *getLoginServer = KIO::get(KURL("https://nexus.passport.com/rdr/pprdr.asp"), true, false); + + getLoginServer->addMetaData("cookies", "manual"); + getLoginServer->addMetaData("cache", "reload"); + getLoginServer->addMetaData("PropagateHttpHeader", "true"); + + connect(getLoginServer, SIGNAL(result(KIO::Job *)), this, SLOT(slotLoginServerReceived(KIO::Job* ))); +} + +void MSNSecureLoginHandler::slotLoginServerReceived(KIO::Job *loginJob) +{ + if(!loginJob->error()) + { + // Retrive the HTTP header + QString httpHeaders = loginJob->queryMetaData("HTTP-Headers"); + + // Get the login URL using QRegExp + QRegExp rx("PassportURLs: DARealm=(.*),DALogin=(.*),DAReg="); + rx.search(httpHeaders); + + // Set the loginUrl and loginServer + QString loginUrl = rx.cap(2); + QString loginServer = loginUrl.section('/', 0, 0); + + kdDebug(14140) << k_funcinfo << loginServer << endl; + + QString authURL = "https://" + loginUrl; + + KIO::Job *authJob = KIO::get(KURL(authURL), true, false); + authJob->addMetaData("cookies", "manual"); + + QString authRequest = "Authorization: Passport1.4 " + "OrgVerb=GET," + "OrgURL=http%3A%2F%2Fmessenger%2Emsn%2Ecom," + "sign-in=" + KURL::encode_string(m_accountId) + + ",pwd=" + KURL::encode_string( m_password ).replace(',',"%2C") + + "," + m_authentification + "\r\n"; + +// warning, this debug contains the password +// kdDebug(14140) << k_funcinfo << "Auth request: " << authRequest << endl; + + authJob->addMetaData("customHTTPHeader", authRequest); + authJob->addMetaData("SendLanguageSettings", "false"); + authJob->addMetaData("PropagateHttpHeader", "true"); + authJob->addMetaData("cookies", "manual"); + authJob->addMetaData("cache", "reload"); + + connect(authJob, SIGNAL(result(KIO::Job *)), this, SLOT(slotTweenerReceived(KIO::Job* ))); + } + else + { + kdDebug(14140) << k_funcinfo << loginJob->errorString() << endl; + + emit loginFailed(); + } +} + +void MSNSecureLoginHandler::slotTweenerReceived(KIO::Job *authJob) +{ + if(!authJob->error()) + { + QString httpHeaders = authJob->queryMetaData("HTTP-Headers"); + +// kdDebug(14140) << k_funcinfo << "HTTP headers: " << httpHeaders << endl; + + // Check if we get "401 Unauthorized", thats means it's a bad password. + if(httpHeaders.contains(QString::fromUtf8("401 Unauthorized"))) + { +// kdDebug(14140) << k_funcinfo << "MSN Login Bad password." << endl; + emit loginBadPassword(); + } + else + { + QRegExp rx("from-PP='(.*)'"); + rx.search(httpHeaders); + QString ticket = rx.cap(1); + + // kdDebug(14140) << k_funcinfo << "Received ticket: " << ticket << endl; + + emit loginSuccesful(ticket); + } + } + else + { + kdDebug(14140) << k_funcinfo << authJob->errorString() << endl; + + emit loginFailed(); + } +} +#include "msnsecureloginhandler.moc" diff --git a/kopete/protocols/msn/msnsecureloginhandler.h b/kopete/protocols/msn/msnsecureloginhandler.h new file mode 100644 index 00000000..8e4dc466 --- /dev/null +++ b/kopete/protocols/msn/msnsecureloginhandler.h @@ -0,0 +1,76 @@ +/* + msnsecureloginhandler.h - SSL login for MSN protocol + + Copyright (c) 2005 by Michaël Larouche <michael.larouche@kdemail.net> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ +#ifndef MSNSECURELOGINHANDLER_H +#define MSNSECURELOGINHANDLER_H + +#include <qobject.h> + +namespace KIO +{ + class Job; + class MetaData; +} + +/** + * This class handle the login process. It connect to the .NET Password service and retrive the ticket(tweener) to login. + * Use KIO. + * + * @author Michaël Larouche <michael.larouche@kdemail.net> +*/ +class MSNSecureLoginHandler : public QObject +{ +Q_OBJECT +public: + MSNSecureLoginHandler(const QString &accountId, const QString &password, const QString &authParameters); + + ~MSNSecureLoginHandler(); + + void login(); + +signals: + /** + * TODO: return to const QString & + */ + void loginSuccesful(QString ticket); + void loginBadPassword(); + void loginFailed(); + +private slots: + void slotLoginServerReceived(KIO::Job *); + /** + * We have received our ticket to login. + */ + void slotTweenerReceived(KIO::Job *); + +private: + /** + * Store the password. + */ + QString m_password; + /** + * Store the accountId. + */ + QString m_accountId; + /** + * Store the authentification parameters + */ + QString m_authentification; + + void displayMetaData(KIO::MetaData data); +}; + +#endif diff --git a/kopete/protocols/msn/msnsocket.cpp b/kopete/protocols/msn/msnsocket.cpp new file mode 100644 index 00000000..a650cd83 --- /dev/null +++ b/kopete/protocols/msn/msnsocket.cpp @@ -0,0 +1,1099 @@ +/* + msnsocket.cpp - Base class for the sockets used in MSN + + Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnsocket.h" +//#include "msnprotocol.h" + +#include <qregexp.h> +#include <qtimer.h> + +#include <kdebug.h> +#include <kconfig.h> +#include <kbufferedsocket.h> +#include <kserversocket.h> +#include <kresolver.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kurl.h> + +#include "kopeteuiglobal.h" + +using namespace KNetwork; + +class MimeMessage +{ + public: + MimeMessage(const QString &msg) : message(msg) {} + + QString getValue(const QString &key) + { + QRegExp rx(key+": ([^\r\n]+)"); + rx.search(message); + return rx.cap(1); + } + private: + QString message; +}; + +MSNSocket::MSNSocket(QObject* parent) : QObject (parent) +{ + m_onlineStatus = Disconnected; + m_socket = 0L; + m_useHttp = false; + m_timer = 0L; +} + +MSNSocket::~MSNSocket() +{ + //if ( m_onlineStatus != Disconnected ) + // disconnect(); + delete m_timer; + m_timer = 0L; + doneDisconnect(); + if ( m_socket ) + m_socket->deleteLater(); +} + +void MSNSocket::connect( const QString &server, uint port ) +{ + if ( m_onlineStatus == Connected || m_onlineStatus == Connecting ) + { + kdWarning( 14140 ) << k_funcinfo << "Already connected or connecting! Not connecting again." << endl; + return; + } + + if( m_onlineStatus == Disconnecting ) + { + // Cleanup first. + // FIXME: More generic!!! + kdWarning( 14140 ) << k_funcinfo << "We're still disconnecting! Deleting socket the hard way first." << endl; + delete m_socket; + } + + setOnlineStatus( Connecting ); + m_id = 0; + //m_lastId = 0; + m_waitBlockSize = 0; + m_buffer = Buffer( 0 ); + + //m_sendQueue.clear(); + + m_server = server; + m_port = port; + + if(!m_useHttp) + m_socket = new KBufferedSocket( server, QString::number(port) ); + else { + m_socket = new KBufferedSocket( m_gateway, "80" ); + } + + m_socket->enableRead( true ); + + // enableWrite eats the CPU, and we only need it when the queue is + // non-empty, so disable it until we have actual data in the queue + m_socket->enableWrite( false ); + + QObject::connect( m_socket, SIGNAL( readyRead() ), this, SLOT( slotDataReceived() ) ); + QObject::connect( m_socket, SIGNAL( readyWrite() ), this, SLOT( slotReadyWrite() ) ); + QObject::connect( m_socket, SIGNAL( hostFound() ), this, SLOT( slotHostFound() ) ); + QObject::connect( m_socket, SIGNAL( connected( const KResolverEntry&) ), this, SLOT( slotConnectionSuccess() ) ); + QObject::connect( m_socket, SIGNAL( gotError( int ) ), this, SLOT( slotSocketError( int ) ) ); + QObject::connect( m_socket, SIGNAL( closed( ) ), this, SLOT( slotSocketClosed( ) ) ); + + if(m_useHttp) + { + if(m_timer == 0L) + { + m_timer = new QTimer(this, "Http poll timer"); + // Connect the slot HttpPoll with the timer timeout signal. + QObject::connect(m_timer, SIGNAL(timeout()), this, SLOT(slotHttpPoll())); + } + } + + aboutToConnect(); + + // start the asynchronous connection + m_socket->connect(); +} + +void MSNSocket::disconnect() +{ + if(m_useHttp) + if(m_timer->isActive()) { + // If the timer is still active, stop the timer. + m_timer->stop(); + } + + if ( m_socket ) + m_socket->closeNow(); + else + slotSocketClosed(); +} + +void MSNSocket::aboutToConnect() +{ + /* Empty default implementation */ +} + +void MSNSocket::doneConnect() +{ + setOnlineStatus( Connected ); +} + +void MSNSocket::doneDisconnect() +{ + setOnlineStatus( Disconnected ); +} + +void MSNSocket::setOnlineStatus( MSNSocket::OnlineStatus status ) +{ + if ( m_onlineStatus == status ) + return; + + m_onlineStatus = status; + emit onlineStatusChanged( status ); +} + +void MSNSocket::slotSocketError( int error ) +{ + kdWarning( 14140 ) << k_funcinfo << "Error: " << error << " (" << m_socket->errorString() << ")" << endl; + + if(!KSocketBase::isFatalError(error)) + return; + //we only care about fatal error + + QString errormsg = i18n( "There was an error while connecting to the MSN server.\nError message:\n" ); + if ( error == KSocketBase::LookupFailure ) + errormsg += i18n( "Unable to lookup %1" ).arg( m_socket->peerResolver().nodeName() ); + else + errormsg += m_socket->errorString() ; + + //delete m_socket; + m_socket->deleteLater(); + m_socket = 0L; + + setOnlineStatus( Disconnected ); + emit connectionFailed(); + //like if the socket is closed + emit socketClosed(); + + emit errorMessage( ErrorConnectionError, errormsg ); +} + +void MSNSocket::slotDataReceived() +{ + int avail = m_socket->bytesAvailable(); + if ( avail < 0 ) + { + // error! + kdWarning( 14140 ) << k_funcinfo << "bytesAvailable() returned " << avail + << ". This should not happen!" << endl + << "Are we disconnected? Backtrace:" << endl << kdBacktrace() << endl; + return; + } + + // incoming data, plus an extra char where we pretend a NUL is so the conversion + // to QCString doesn't go over the end of the allocated memory. + char *buffer = new char[ avail + 1 ]; + int ret = m_socket->readBlock( buffer, avail ); + + if ( ret < 0 ) + { + kdWarning( 14140 ) << k_funcinfo << "readBlock() returned " << ret << "!" <<endl; + } + else if ( ret == 0 ) + { + kdWarning( 14140 ) << k_funcinfo << "readBlock() returned no data!" <<endl; + } + else + { + if ( avail ) + { + if ( ret != avail) + { + kdWarning( 14140 ) << k_funcinfo << avail << " bytes were reported available, " + << "but readBlock() returned only " << ret << " bytes! Proceeding anyway." << endl; + } + } + else + { + kdDebug( 14140 ) << k_funcinfo << "Read " << ret << " bytes into 4kb block." << endl; + } + + + QString rawData; + + if(m_useHttp) + { + bool error = false; + QByteArray bytes; + + // Check if all data has arrived. + rawData = QString(QCString(buffer, avail + 1)); + bool headers = (rawData.find(QRegExp("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)")) != -1); + + if(headers) + { + // The http header packet arrived. + int endOfHeaders = rawData.find("\r\n\r\n"); + if((endOfHeaders + 4) == avail) + { + // Only the response headers data is included. + QRegExp re("Content-Length: ([^\r\n]+)"); + if(re.search(rawData) != -1) + { + bool valid; + int l = re.cap(1).toInt(&valid); + if(valid && l > 0) + { + // The packet contains the headers but does not contain the content data; + // buffer the data received and read again. + m_buffer.add(buffer, avail); + + delete[] buffer; + // Update how much data remains. + m_remaining = l; + return; + } + } + } + } + else + { + // Write the received data to the buffer. + m_buffer.add(buffer, avail); + + m_remaining -= avail; + if(m_remaining != 0) + { + // We have not received all the content data, read again. + delete[] buffer; + return; + } + + // At this point, we have all the bytes returned from the web request. + bytes = m_buffer.take(m_buffer.size()); + } + + if(bytes.size() == 0) + { + // The response headers and the content came in one packet. + bytes.assign(buffer, avail); + } + + + // Create the web response object from the response bytes. + WebResponse response(bytes); + + if(response.getStatusCode() == 100) { + return; + } + + if(response.getStatusCode() == 200) + { + // If we received a valid response, read the required headers. + // Retrieve the X-MSN-Messenger header. + QString header = response.getHeaders()->getValue("X-MSN-Messenger"); + + QStringList parts = QStringList::split(";", header.replace(" ", "")); + if(!header.isNull() && (parts.count() >= 2)) + { + if(parts[0].find("SessionID", 0) != -1) + { + // Assign the session id. + m_sessionId = parts[0].section("=", 1, 1); + }else + error = true; + + if(parts[1].find("GW-IP", 0) != -1) + { + // Assign the gateway IP address. + m_gwip = parts[1].section("=", 1, 1); + }else + error = true; + + + if(parts.count() > 2) + if((parts[2].find("Session", 0) != -1) && (parts[2].section("=", 1, 1) == "close")) + { + // The http session has been closed by the server, disconnect. + kdDebug(14140) << k_funcinfo << "Session closed." << endl; + m_bCanPoll = false; + disconnect(); + return; + } + }else + error = true; + + // Retrieve the content length header. + header = response.getHeaders()->getValue("Content-Length"); + + if(!header.isNull()) + { + bool valid; + int length = header.toInt(&valid); + if(valid && (length == 0)) + { + // If the response content length is zero, there is nothing to do. + m_pending = false; + return; + } + + if(valid && (length > 0)) + { + // Otherwise, if the content length is greater than zero, get the web response stream. + QDataStream *stream = response.getResponseStream(); + buffer = new char[length]; + // Read the web response content. + stream->readRawBytes(buffer, length); + ret = length; + }else + error = true; + }else + error = true; + }else + error = true; + + if(error) + { + kdDebug(14140) << k_funcinfo << "Http error: " << response.getStatusCode() << " " + << response.getStatusDescription() << endl; + + // If we encountered an error, disconnect and return. + m_bCanPoll = false; + // Disconnect from the service. + disconnect(); + return; + } + } + + // Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug: + // all MSN commands start with one or more uppercase characters. + // For now just check the first three chars, let's see how accurate it is. + // Additionally, if we receive an MSN-P2P packet, strip off anything after the P2P header. + rawData = QString( QCString( buffer, ((!m_useHttp)? avail : ret) + 1 ) ).stripWhiteSpace().replace( + QRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" ); + + bool isBinary = false; + for ( uint i = 0; i < 3 ; ++i ) + { + if ( (rawData[ i ] < 'A' || rawData[ i ] > 'Z') && (rawData[ i ] < '0' || rawData[ i ] > '9') ) + isBinary = true; + } + + if ( isBinary ) + kdDebug( 14141 ) << k_funcinfo << "(Stripped binary data)" << endl; + else + kdDebug( 14141 ) << k_funcinfo << rawData << endl; + + // fill the buffer with the received data + m_buffer.add( buffer, ret ); + + slotReadLine(); + + if(m_useHttp) { + // Set data pending to false. + m_pending = false; + } + } + + // Cleanup. + delete[] buffer; +} + +void MSNSocket::slotReadLine() +{ + // We have data, first check if it's meant for a block read, otherwise + // parse the first line (which will recursively parse the other lines) + if ( !pollReadBlock() ) + { + if ( m_buffer.size() >= 3 && ( m_buffer.data()[ 0 ] == '\0' || m_buffer.data()[ 0 ]== '\1' ) ) + { + bytesReceived( m_buffer.take( 3 ) ); + QTimer::singleShot( 0, this, SLOT( slotReadLine() ) ); + return; + } + + int index = -1; + for ( uint x = 0; m_buffer.size() > x + 1; ++x ) + { + if ( ( m_buffer[ x ] == '\r' ) && ( m_buffer[ x + 1 ] == '\n' ) ) + { + index = x; + break; + } + } + + if ( index != -1 ) + { + QString command = QString::fromUtf8( m_buffer.take( index + 2 ), index ); + command.replace( "\r\n", "" ); + //kdDebug( 14141 ) << k_funcinfo << command << endl; + + // Don't block the GUI while parsing data, only do a single line! + // (Done before parseLine() to prevent a potential crash) + QTimer::singleShot( 0, this, SLOT( slotReadLine() ) ); + + parseLine( command ); + // WARNING: At this point 'this' can be deleted (when disconnecting) + } + } +} + +void MSNSocket::readBlock( uint len ) +{ + if ( m_waitBlockSize ) + { + kdWarning( 14140 ) << k_funcinfo << "Cannot wait for data block: still waiting for other block of size " + << m_waitBlockSize << "! Data will not be returned." << endl; + return; + } + + m_waitBlockSize = len; + + //kdDebug( 14140 ) << k_funcinfo << "Preparing for block read of size " << len << endl; + + // Try to return the data now, if available. Otherwise slotDataReady + // will do this whenever all data is there. + pollReadBlock(); +} + +bool MSNSocket::pollReadBlock() +{ + if ( !m_waitBlockSize ) + { + return false; + } + else if ( m_buffer.size() < m_waitBlockSize ) + { + kdDebug( 14140 ) << k_funcinfo << "Waiting for data. Received: " << m_buffer.size() << ", required: " << m_waitBlockSize << endl; + return true; + } + + QByteArray block = m_buffer.take( m_waitBlockSize ); + + //kdDebug( 14140 ) << k_funcinfo << "Successfully read block of size " << m_waitBlockSize << endl; + + m_waitBlockSize = 0; + emit blockRead( block); + + return false; +} + +void MSNSocket::parseLine( const QString &str ) +{ + QString cmd = str.section( ' ', 0, 0 ); + QString data = str.section( ' ', 2 ).replace( "\r\n" , "" ); + + bool isNum; + uint id = str.section( ' ', 1, 1 ).toUInt( &isNum ); + + // In some rare cases, like the 'NLN' / 'FLN' commands no id at all + // is sent. Here it's actually a real parameter... + if ( !isNum ) + data = str.section( ' ', 1, 1 ) + " " + data; + + //if ( isNum && id ) + // m_lastId = id; + + //kdDebug( 14140 ) << k_funcinfo << "Parsing command " << cmd << " (ID " << id << "): '" << data << "'" << endl; + + data.replace( "\r\n", "" ); + bool isError; + uint errorCode = cmd.toUInt( &isError ); + if ( isError ) + handleError( errorCode, id ); + else + parseCommand( cmd, id, data ); +} + +void MSNSocket::handleError( uint code, uint /* id */ ) +{ + kdDebug(14140) << k_funcinfo << endl; + QString msg; + ErrorType type = ErrorServerError; + switch ( code ) + { +/* + // We cant show message for error we don't know what they are or not related to the correct socket + // Theses following messages are not so instructive + case 205: + msg = i18n ( "An invalid username has been specified.\nPlease correct it, and try to reconnect.\n" ); + break; + case 201: + msg = i18n ( "Fully Qualified domain name missing.\n" ); + break; + case 207: + msg = i18n ( "You are already logged in!\n" ); + break; + case 208: + msg = i18n ( "You specified an invalid username.\nPlease correct it, and try to reconnect.\n"); + break; + case 209: + msg = i18n ( "Your nickname is invalid. Please check it, correct it,\nand try to reconnect.\n" ); + break; + case 210: + msg = i18n ( "Your list has reached its maximum capacity.\nNo more contacts can be added, unless you remove some first.\n" ); + break; + case 216: + msg = i18n ( "This user is not in your contact list.\n " ); + break; + case 300: + msg = i18n ( "Some required fields are missing. Please fill them in and try again.\n" ); + break; + case 302: + msg = i18n ( "You are not logged in.\n" ); + break; +*/ + case 500: + msg = i18n ( "An internal server error occurred. Please try again later." ); + type = MSNSocket::ErrorCannotConnect; + break; + case 502: + msg = i18n ( "It is no longer possible to perform this operation. The MSN server does not allow it anymore." ); + type = MSNSocket::ErrorServerError; + break; + case 600: + case 910: + case 912: + case 921: + case 922: + msg = i18n ( "The MSN server is busy. Please try again later." ); + type = MSNSocket::ErrorConnectionError; + break; + case 601: + case 604: + case 605: + case 914: + case 915: + case 916: + case 917: + msg = i18n ( "The server is not available at the moment. Please try again later." ); + type = MSNSocket::ErrorCannotConnect; + break; + // Server error + default: + // FIXME: if the error causes a disconnect, it will crash, but we can't disconnect every time + msg = i18n( "Unhandled MSN error code %1 \n" + "Please fill a bug report with a detailed description and if possible the last console debug output." ).arg( code ); + // "See http://www.hypothetic.org/docs/msn/basics.php for a description of all error codes." + break; + } + + if ( !msg.isEmpty() ) + emit errorMessage( type, msg ); + + return; +} + +int MSNSocket::sendCommand( const QString &cmd, const QString &args, bool addId, const QByteArray &body, bool binary ) +{ + if ( !m_socket ) + { + kdWarning( 14140 ) << k_funcinfo << "m_socket == NULL!" << endl; + return -1; + } + + QCString data = cmd.utf8(); + if ( addId ) + data += " " + QString::number( m_id ).utf8(); + + if ( !args.isEmpty() ) + data += " " + args.utf8(); + + // Add length in bytes, not characters + if ( !body.isEmpty() ) + data += " " + QString::number( body.size() - (binary ? 0 : 1 ) ).utf8(); + + data += "\r\n"; + + + // the command will be sent in slotReadyWrite + QByteArray bytes; + const uint length = data.length(); + bytes.duplicate(data.data(), length); + if(!body.isEmpty()) + { + uint l = body.size() - (binary ? 0 : 1); + bytes.resize(length + l); + for(uint i=0; i < l; i++) + bytes[length + i] = body[i]; + } + + // Add the request to the queue. + m_sendQueue.append(bytes); + m_socket->enableWrite(true); + + if ( addId ) + { + ++m_id; + return m_id - 1; + } + + return 0; +} + +void MSNSocket::slotReadyWrite() +{ + if ( !m_sendQueue.isEmpty() ) + { + // If the command queue is not empty, retrieve the first command. + QValueList<QByteArray>::Iterator it = m_sendQueue.begin(); + + if(m_useHttp) + { + // If web response data is not pending, send the http request. + if(!m_pending) + { + m_pending = true; + // Temporarily disable http polling. + m_bCanPoll = false; + // Set the host to the msn gateway by default. + QString host = m_gateway; + QString query; // Web request query string. + + if(m_bIsFirstInTransaction) + { + query.append("Action=open&Server="); + query.append(m_type); + + query += "&IP=" + m_server; + + m_bIsFirstInTransaction = false; + } + else + { + // If this is not the first request sent in the transaction, + // only add the session Id. + host = m_gwip; + query += "SessionID=" + m_sessionId; + } + + // Create the web request headers. + QString s = makeHttpRequestString(host, query, (*it).size()); + + uint length = s.length(); + // Create the web request bytes. + QByteArray bytes(length + (*it).size()); + + // Copy the request headers into the request bytes. + for(uint i=0; i < length; i++) + bytes[i] = s.ascii()[i]; + // Copy the request body into the request bytes. + for(uint i=0; i < (*it).size(); i++) + bytes[length + i] = (*it)[i]; + + kdDebug( 14141 ) << k_funcinfo << "Sending http command: " << QString(*it).stripWhiteSpace() << endl; + + // Write the request bytes to the socket. + m_socket->writeBlock(bytes.data(), bytes.size()); + + // Remove the request from the request queue. + m_sendQueue.remove(it); + + if(m_sendQueue.isEmpty()) + { + // Disable sending requests. + m_socket->enableWrite(false); + // If the request queue is empty, poll the server. + m_bCanPoll = true; + } + } + } + else + { + // Otherwise, send the command normally. + + // Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug: + // When sending an MSN-P2P packet, strip off anything after the P2P header. + QString debugData = QString( *it ).stripWhiteSpace().replace( + QRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" ); + kdDebug( 14141 ) << k_funcinfo << "Sending command: " << debugData << endl; + + m_socket->writeBlock( *it, ( *it ).size() ); + m_sendQueue.remove( it ); + + // If the queue is empty agalin stop waiting for readyWrite signals + // because of the CPU usage + if ( m_sendQueue.isEmpty() ) + m_socket->enableWrite( false ); + } + } + else + { + m_socket->enableWrite( false ); + + if(m_useHttp) + { + // If the request queue is empty, poll the server. + m_bCanPoll = true; + } + } +} + +QString MSNSocket::escape( const QString &str ) +{ + //return ( KURL::encode_string( str, 106 ) ); + //It's not needed to encode everything. The official msn client only encode spaces and % + //If we encode more, the size can be longer than excepted. + + int old_length= str.length(); + QChar *new_segment = new QChar[ old_length * 3 + 1 ]; + int new_length = 0; + + for ( int i = 0; i < old_length; i++ ) + { + unsigned short character = str[i].unicode(); + + if( character <= 32 || character == '%' ) + { + new_segment[ new_length++ ] = '%'; + + unsigned int c = character / 16; + c += (c > 9) ? ('A' - 10) : '0'; + new_segment[ new_length++ ] = c; + + c = character % 16; + c += (c > 9) ? ('A' - 10) : '0'; + new_segment[ new_length++ ] = c; + } + else + new_segment[ new_length++ ] = str[i]; + } + + QString result = QString(new_segment, new_length); + delete [] new_segment; + return result; + +} + +QString MSNSocket::unescape( const QString &str ) +{ + QString str2 = KURL::decode_string( str, 106 ); + //remove msn+ colors code + str2 = str2.replace( QRegExp("[\\x1-\\x8]"), "" ); // old msn+ colors + // added by kaoul <erwin.kwolek at gmail.com> + str2 = str2.replace( QRegExp("\\xB7[&@\'#0]"),""); // dot ... + str2 = str2.replace( QRegExp("\\xB7\\$,?\\d{1,2}"),""); // dot dollar (comma)? 0-99 + + return str2; +} + +void MSNSocket::slotConnectionSuccess() +{ + if(m_useHttp) + { + // If we are connected, set the data pending flag to false, + // and disable http polling. + m_pending = false; + m_bCanPoll = false; + // If we are connected, start the timer. + m_timer->start(2000, false); + } + + //kdDebug( 14140 ) << k_funcinfo << endl; + doneConnect(); +} + +void MSNSocket::slotHostFound() +{ + // nothing to do +} + +void MSNSocket::slotSocketClosed() +{ + kdDebug( 14140 ) << k_funcinfo << "Socket closed. " << endl; + + if ( !m_socket || m_onlineStatus == Disconnected ) + { + kdDebug( 14140 ) << k_funcinfo << "Socket already deleted or already disconnected" << endl; + return; + } + + doneDisconnect(); + + m_buffer = Buffer( 0 ); + //delete m_socket; + m_socket->deleteLater(); + m_socket = 0L; + + emit socketClosed(); +} + +void MSNSocket::slotHttpPoll() +{ + if(m_pending || !m_bCanPoll){ + // If data is pending or poll has been temporary disabled, return. + return; + } + + // Create the http request headers. + const QCString headers = makeHttpRequestString(m_gwip, "Action=poll&SessionID=" + m_sessionId, 0).utf8(); + m_socket->writeBlock(headers, headers.length()); + // Wait for the response. + m_pending = true; + m_socket->enableWrite(true); +} + +// Used in MSNFileTransferSocket +// FIXME: Why is this here if it's only used for file transfer? - Martijn +void MSNSocket::bytesReceived( const QByteArray & /* data */ ) +{ + kdWarning( 14140 ) << k_funcinfo << "Unknown bytes were received" << endl; +} + +void MSNSocket::sendBytes( const QByteArray &data ) +{ + if ( !m_socket ) + { + kdWarning( 14140 ) << k_funcinfo << "Not yet connected" << endl; + return; + } + + m_socket->writeBlock( data, data.size() ); + m_socket->enableWrite( true ); +} + +bool MSNSocket::setUseHttpMethod( bool useHttp ) +{ + if( m_useHttp == useHttp ) + return true; + + if( useHttp ) { + QString s = QString( this->className() ).lower(); + if( s == "msnnotifysocket" ) + m_type = "NS"; + else if( s == "msnswitchboardsocket" ) + m_type = "SB"; + else + m_type = QString::null; + + if( m_type.isNull() ) + return false; + + m_bCanPoll = false; + m_bIsFirstInTransaction = true; + m_pending = false; + m_remaining = 0; + m_gateway = "gateway.messenger.hotmail.com"; + } + + if ( m_onlineStatus != Disconnected ) + disconnect(); + + m_useHttp = useHttp; + + return true; +} + +bool MSNSocket::useHttpMethod() const +{ + return m_useHttp; +} + +bool MSNSocket::accept( KServerSocket *server ) +{ + if ( m_socket ) + { + kdWarning( 14140 ) << k_funcinfo << "Socket already exists!" << endl; + return false; + } + + m_socket = static_cast<KBufferedSocket*>(server->accept()); + + if ( !m_socket ) + { +// kdWarning( 14140 ) << k_funcinfo << "Socket not created. Error nb" << server->error() << " : " << server->errorString() << endl; + return false; + } + + kdDebug( 14140 ) << k_funcinfo << "incoming connection accepted" << endl; + + setOnlineStatus( Connecting ); + + m_id = 0; + //m_lastId = 0; + m_waitBlockSize = 0; + + m_socket->setBlocking( false ); + m_socket->enableRead( true ); + m_socket->enableWrite( true ); + + QObject::connect( m_socket, SIGNAL( readyRead() ), this, SLOT( slotDataReceived() ) ); + QObject::connect( m_socket, SIGNAL( readyWrite() ), this, SLOT( slotReadyWrite() ) ); + QObject::connect( m_socket, SIGNAL( closed() ), this, SLOT( slotSocketClosed() ) ); + QObject::connect( m_socket, SIGNAL( gotError( int ) ), this, SLOT( slotSocketError( int ) ) ); + + doneConnect(); + return true; +} + +QString MSNSocket::getLocalIP() +{ + if ( !m_socket ) + return QString::null; + + const KSocketAddress address = m_socket->localAddress(); + + QString ip = address.nodeName(); + + kdDebug( 14140 ) << k_funcinfo << "IP: " << ip <<endl; + //delete address; + return ip; +} + +MSNSocket::Buffer::Buffer( unsigned int sz ) +: QByteArray( sz ) +{ +} + +MSNSocket::Buffer::~Buffer() +{ +} + +void MSNSocket::Buffer::add( char *str, unsigned int sz ) +{ + char *b = new char[ size() + sz ]; + for ( uint f = 0; f < size(); f++ ) + b[ f ] = data()[ f ]; + for ( uint f = 0; f < sz; f++ ) + b[ size() + f ] = str[ f ]; + + duplicate( b, size() + sz ); + delete[] b; +} + +QByteArray MSNSocket::Buffer::take( unsigned blockSize ) +{ + if ( size() < blockSize ) + { + kdWarning( 14140 ) << k_funcinfo << "Buffer size " << size() << " < asked size " << blockSize << "!" << endl; + return QByteArray(); + } + + QByteArray rep( blockSize ); + for( uint i = 0; i < blockSize; i++ ) + rep[ i ] = data()[ i ]; + + char *str = new char[ size() - blockSize ]; + for ( uint i = 0; i < size() - blockSize; i++ ) + str[ i ] = data()[ blockSize + i ]; + duplicate( str, size() - blockSize ); + delete[] str; + + return rep; +} + +QString MSNSocket::makeHttpRequestString(const QString& host, const QString& query, uint contentLength) +{ + QString s( + "POST http://" + host + "/gateway/gateway.dll?" + query + " HTTP/1.1\r\n" + + "Accept: */*\r\n" + + "Accept-Language: en-us\r\n" + + "User-Agent: MSMSGS\r\n" + + "Host: " + host + "\r\n" + + "Proxy-Connection: Keep-Alive\r\n" + + "Connection: Keep-Alive\r\n" + + "Pragma: no-cache\r\n" + + "Content-Type: application/x-msn-messenger\r\n" + + "Content-Length: " + QString::number(contentLength) + "\r\n" + + "\r\n"); + return s; +} + +MSNSocket::WebResponse::WebResponse(const QByteArray& bytes) +{ + m_statusCode = 0; + m_stream = 0; + + int headerEnd; + QString header; + QString data(QCString(bytes, bytes.size() + 1)); + + // Parse the HTTP status header + QRegExp re("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)"); + headerEnd = data.find("\r\n"); + header = data.left( (headerEnd == -1) ? 20 : headerEnd ); + + re.search(header); + m_statusCode = re.cap(1).toInt(); + m_statusDescription = re.cap(2); + + // Remove the web response status header. + data = data.mid(headerEnd + 2, (data.find("\r\n\r\n") + 2) - (headerEnd + 2)); + // Create a MimeMessage, removing the HTTP status header + m_headers = new MimeMessage(data); + + // Retrieve the contentlength header. + header = m_headers->getValue("Content-Length"); + if(!header.isNull()) + { + bool valid; + int length = header.toInt(&valid); + if(valid && length > 0) + { + // If the content length is valid, and not zero, + // copy the web response content bytes. + int offset = bytes.size() - length; + + QByteArray content(length); + for(int i=0; i < length; i++) + content[i] = bytes[offset + i]; + // Create the web response stream from the response content bytes. + m_stream = new QDataStream(content, IO_ReadOnly); + } + } +} + +MSNSocket::WebResponse::~WebResponse() +{ + delete m_headers; + m_headers = 0; + delete m_stream; + m_stream = 0; +} + +MimeMessage* MSNSocket::WebResponse::getHeaders() +{ + return m_headers; +} + +QDataStream* MSNSocket::WebResponse::getResponseStream() +{ + return m_stream; +} + +int MSNSocket::WebResponse::getStatusCode() +{ + return m_statusCode; +} + +QString MSNSocket::WebResponse::getStatusDescription() +{ + return m_statusDescription; +} + + +#include "msnsocket.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnsocket.h b/kopete/protocols/msn/msnsocket.h new file mode 100644 index 00000000..85df08c7 --- /dev/null +++ b/kopete/protocols/msn/msnsocket.h @@ -0,0 +1,362 @@ +/* + msnsocket.h - Base class for the sockets used in MSN + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2004 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + Kopete (c) 2002 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNSOCKET_H +#define MSNSOCKET_H + +#include <qobject.h> +#include <qdatastream.h> +#include <qstringlist.h> +#include <qtimer.h> +#include <qvaluelist.h> + +#include "kopete_export.h" + +namespace KNetwork { + class KBufferedSocket; + class KServerSocket; +} + +class MimeMessage; + +/** + * @author Martijn Klingens <klingens@kde.org> + * + * MSNSocket encapsulates the common functionality shared by the Dispatch + * Server, the Notification Server and the Switchboard Server. It is + * inherited by the various specialized classes. + */ +class KOPETE_EXPORT MSNSocket : public QObject +{ + Q_OBJECT + +public: + MSNSocket(QObject* parent=0l); + ~MSNSocket(); + + /** + * Asynchronously read a block of data of the specified size. When the + * data is available, the blockRead() signal will be emitted with the + * data as parameter. + * + * NOTE: As the block queue takes precedence over the line-based + * command-processing this method can effectively block all + * communications when passed a wrong length! + */ + void readBlock( uint len ); + + /** + * OnlineStatus encapsulates the 4 states a connection can be in, + * Connecting, Connected, Disconnecting, Disconnected. Connecting + * and Disconnecting are in the default implementation not used, + * because the socket connect is an atomic operation and not yet + * performed asynchronously. + * In derived classes, like the Notification Server, this state is + * actively used, because merely having a socket connection established + * by no means indicates we're actually online - the rest of the + * handshake likely has to follow first. + */ + enum OnlineStatus { Connecting, Connected, Disconnecting, Disconnected }; + enum LookupStatus { Processing, Success, Failed }; + enum Transport { TcpTransport, HttpTransport }; + enum ErrorType { ErrorConnectionLost, ErrorConnectionError, ErrorCannotConnect, ErrorServerError, ErrorInformation}; + + OnlineStatus onlineStatus() { return m_onlineStatus; } + + /* + * return the local ip. + * Used for filetransfer + */ + QString getLocalIP(); + + //BEGIN Http + + virtual bool setUseHttpMethod( bool useHttp ); + bool useHttpMethod() const; + + //END + +public slots: + void connect( const QString &server, uint port ); + virtual void disconnect(); + + + /** + * Send an MSN command to the socket + * + * For debugging it's convenient to have this method public, but using + * it outside this class is deprecated for any other use! + * + * The size of the body (if any) is automatically added to the argument + * list and shouldn't be explicitly specified! This size is in bytes + * instead of characters to reflect what actually goes over the wire. + * + * if the param binary is set to true, then, the body is send as a binary message + * + * return the id + */ + int sendCommand( const QString &cmd, const QString &args = QString::null, + bool addId = true, const QByteArray &body = QByteArray() , bool binary=false ); + +signals: + /** + * A block read is ready. + * After this the normal line-based reads go on again + */ + void blockRead( const QByteArray &block ); + + /** + * The online status has changed + */ + void onlineStatusChanged( MSNSocket::OnlineStatus status ); + + /** + * The connection failed + */ + void connectionFailed(); + + /** + * The connection was closed + */ + void socketClosed(); + + /** + * A error has occured. Handle the display of the message. + */ + void errorMessage( int type, const QString &msg ); + +protected: + /** + * Convenience method: escape spaces with '%20' for use in the protocol. + * Doesn't escape any other sequence. + */ + QString escape( const QString &str ); + + /** + * And the other way round... + */ + QString unescape( const QString &str ); + + /** + * Set the online status. Emits onlineStatusChanged. + */ + void setOnlineStatus( OnlineStatus status ); + + /** + * This method is called directly before the socket will actually connect. + * Override in derived classes to setup whatever is needed before connect. + */ + virtual void aboutToConnect(); + + /** + * Directly after the connect, this method is called. The default + * implementation sets the OnlineStatus to Connected, be sure to override + * this if a handshake is required. + */ + virtual void doneConnect(); + + /** + * Directly after the disconnect, this method is called before the actual + * cleanup takes place. The socket is close here. Cleanup internal + * variables here. + */ + virtual void doneDisconnect(); + + /** + * Handle an MSN error condition. + * The default implementation displays a generic error message and + * closes the connection. Override to allow more graceful handling and + * possibly recovery. + */ + virtual void handleError( uint code, uint id ); + + /** + * Handle an MSN command response line. + * This method is pure virtual and *must* be overridden in derived + * classes. + */ + virtual void parseCommand( const QString &cmd, uint id, + const QString &data ) = 0; + + /** + * Used in MSNFileTransferSocket + */ + virtual void bytesReceived( const QByteArray & ); + bool accept( KNetwork::KServerSocket * ); + void sendBytes( const QByteArray &data ); + + const QString &server() { return m_server; } + uint port() { return m_port; } + + /** + * The last confirmed ID by the server + */ + //uint m_lastId; + +private slots: + void slotDataReceived(); + /** + * If the socket emits a connectionFailed() then this slot is called + * to handle the error. + */ + void slotSocketError( int error ); + + /* + * Calls connectDone() when connection is successfully established. + */ + void slotConnectionSuccess(); + + /** + * Sets m_lookupProgress to 'Finished' if count > 0 or 'Failed' if count = 0. + */ + void slotHostFound( ); + + /** + * Check if new lines of data are available and process the first line + */ + void slotReadLine(); + + void slotSocketClosed(); + + //BEGIN Http + + /** + * Sends a poll request to the msn gateway when using HttpTransport. + * equivalent to sending a PNG command over TcpTransport. + */ + void slotHttpPoll(); + + //END + +protected slots: + virtual void slotReadyWrite(); + +private: + /** + * Check if we're waiting for a block of raw data. Emits blockRead() + * when the data is available. + * Returns true when still waiting and false when there is no pending + * read, or when the read is successfully handled. + */ + bool pollReadBlock(); + + /** + * The id of the message sent to the MSN server. This ID will increment + * for each subsequent message sent. + */ + uint m_id; + + /** + * Queue of pending commands (should be mostly empty, but is needed to + * send more than one command to the server) + */ + QValueList<QByteArray> m_sendQueue; + + /** + * Parse a single line of data. + * Will call either parseCommand or handleError depending on the type of + * data received. + */ + void parseLine( const QString &str ); + + KNetwork::KBufferedSocket *m_socket; + OnlineStatus m_onlineStatus; + + QString m_server; + uint m_port; + + /** + * The size of the requested block for block-based reads + */ + uint m_waitBlockSize; + + class Buffer : public QByteArray + { + public: + Buffer( unsigned size = 0 ); + ~Buffer(); + void add( char *str, unsigned size ); + QByteArray take( unsigned size ); + }; + + Buffer m_buffer; + + //BEGIN Http + + /** + * Makes a http request headers string using the specified, host, query, and content length. + * return: The string containing the http request headers. + */ + QString makeHttpRequestString(const QString& host, const QString& query, uint contentLength); + + bool m_useHttp; // Indicates whether to use the msn http gateway to connect to the msn service. + bool m_bCanPoll; // Indicates whether polling of the http server is allowed. + bool m_bIsFirstInTransaction; // Indicates whether pending message to be sent is the first in the transaction. + // If so, the gateway is used. + // Use the gateway only for initial connected state; Otherwise, use the host. + QString m_gateway; // Msn http gateway domain name. + QString m_gwip; // The ip address of the msn gateway. + QString m_sessionId; // session id. + QTimer *m_timer; // Msn http poll timer. + QString m_type; // Indicates the type of socket being used. NS or SB + bool m_pending; // Indicates whether a http response is pending. + int m_remaining; // Indicates how many bytes of content data remain + // to be received if the content bytes are sent in + // a seperate packet(s). + + /** + * Provides access to information returned from a URI request. + */ + class WebResponse + { + public: + WebResponse(const QByteArray& bytes); + ~WebResponse(); + + /** + * Gets the headers associated with this response from the server. + */ + MimeMessage* getHeaders(); + /** + * Gets the data stream used to read the body of the response from the server. + */ + QDataStream* getResponseStream(); + /** + * Gets the status code of the response. + */ + int getStatusCode(); + /** + * Gets the status description returned with the response. + */ + QString getStatusDescription(); + + private: + MimeMessage *m_headers; + QDataStream *m_stream; + int m_statusCode; + QString m_statusDescription; + }; + + //END +}; + +#endif + diff --git a/kopete/protocols/msn/msnswitchboardsocket.cpp b/kopete/protocols/msn/msnswitchboardsocket.cpp new file mode 100644 index 00000000..ae09a93c --- /dev/null +++ b/kopete/protocols/msn/msnswitchboardsocket.cpp @@ -0,0 +1,1142 @@ +/* + msnswitchboardsocket.cpp - switch board connection socket + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2006 by Olivier Goffart <ogoffart@ kde.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include "msnswitchboardsocket.h" + +#include <stdlib.h> +#include <time.h> +#include <cmath> + +// qt +#include <qstylesheet.h> +#include <qregexp.h> +#include <qimage.h> +#include <qtimer.h> +#include <qfile.h> +#include <qfileinfo.h> + +// kde +#include <kdebug.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kaboutdata.h> +#include <ktempfile.h> +#include <kconfig.h> +#include <kmdcodec.h> +#include <kstandarddirs.h> +#include <ktempfile.h> + +// for the display picture +#include <msncontact.h> +#include "msnnotifysocket.h" + +//kopete +#include "msnaccount.h" +#include "msnprotocol.h" +#include "kopetemessage.h" +#include "kopetecontact.h" +#include "kopeteuiglobal.h" +#include "private/kopeteemoticons.h" +//#include "kopeteaccountmanager.h" +//#include "kopeteprotocol.h" + +#include "sha1.h" + +#include "dispatcher.h" +using P2P::Dispatcher; + +MSNSwitchBoardSocket::MSNSwitchBoardSocket( MSNAccount *account , QObject *parent ) +: MSNSocket( parent ) +{ + m_account = account; + m_recvIcons=0; + m_emoticonTimer=0L; + m_chunks=0; + m_clientcapsSent=false; + m_dispatcher = 0l; + m_keepAlive = 0l; + m_keepAliveNb=0; +} + +MSNSwitchBoardSocket::~MSNSwitchBoardSocket() +{ + kdDebug(14140) << k_funcinfo << endl; + + QMap<QString , QPair<QString , KTempFile*> >::Iterator it; + for ( it = m_emoticons.begin(); it != m_emoticons.end(); ++it ) + { + delete it.data().second; + } +} + +void MSNSwitchBoardSocket::connectToSwitchBoard(QString ID, QString address, QString auth) +{ + // we need these for the handshake later on (when we're connected) + m_ID = ID; + m_auth = auth; + + QString server = address.left( address.find( ":" ) ); + uint port = address.right( address.length() - address.findRev( ":" ) - 1 ).toUInt(); + + QObject::connect( this, SIGNAL( blockRead( const QByteArray & ) ), + this, SLOT(slotReadMessage( const QByteArray & ) ) ); + + QObject::connect( this, SIGNAL( onlineStatusChanged( MSNSocket::OnlineStatus ) ), + this, SLOT( slotOnlineStatusChanged( MSNSocket::OnlineStatus ) ) ); + + QObject::connect( this, SIGNAL( socketClosed( ) ), + this, SLOT( slotSocketClosed( ) ) ); + + connect( server, port ); +} + +void MSNSwitchBoardSocket::handleError( uint code, uint id ) +{ + kdDebug(14140) << k_funcinfo << endl; + + QString msg; + MSNSocket::ErrorType type; + + switch( code ) + { + case 208: + { + msg = i18n( "Invalid user:\n" + "this MSN user does not exist; please check the MSN ID." ); + type = MSNSocket::ErrorServerError; + + userLeftChat(m_msgHandle , i18n("user never joined")); + break; + } + case 215: + { + msg = i18n( "The user %1 is already in this chat." ).arg( m_msgHandle ); + type = MSNSocket::ErrorServerError; + + //userLeftChat(m_msgHandle , i18n("user was twice in this chat") ); //(the user shouln't join there + break; + } + case 216: + { + msg = i18n( "The user %1 is online but has blocked you:\nyou can not talk to this user." ).arg( m_msgHandle ); + type = MSNSocket::ErrorInformation; + + userLeftChat(m_msgHandle, i18n("user blocked you")); + break; + } + case 217: + { + // TODO: we need to know the nickname instead of the handle. + msg = i18n( "The user %1 is currently not signed in.\n" "Messages will not be delivered." ).arg( m_msgHandle ); + type = MSNSocket::ErrorServerError; + + userLeftChat(m_msgHandle, i18n("user disconnected")); + break; + } + case 713: + { + QString msg = i18n( "You are trying to invite too many contacts to this chat at the same time" ).arg( m_msgHandle ); + type = MSNSocket::ErrorInformation; + + userLeftChat(m_msgHandle, i18n("user blocked you")); + break; + } + case 911: + { + msg = i18n("Kopete MSN plugin has trouble authenticating with switchboard server."); + type = MSNSocket::ErrorServerError; + + break; + } + default: + MSNSocket::handleError( code, id ); + break; + } + + if( !msg.isEmpty() ) + emit errorMessage( type, msg ); +} + +void MSNSwitchBoardSocket::parseCommand( const QString &cmd, uint id , + const QString &data ) +{ + if( cmd == "NAK" ) + { + emit msgAcknowledgement(id, false); // msg was not accepted + } + else if( cmd == "ACK" ) + { + emit msgAcknowledgement(id, true); // msg has received + } + else if( cmd == "JOI" ) + { + // new user joins the chat, update user in chat list + QString handle = data.section( ' ', 0, 0 ); + QString screenname = unescape(data.section( ' ', 1, 1 )); + if( !m_chatMembers.contains( handle ) ) + m_chatMembers.append( handle ); + emit userJoined( handle, screenname, false ); + } + else if( cmd == "IRO" ) + { + // we have joined a multi chat session- this are the users in this chat + QString handle = data.section( ' ', 2, 2 ); + if( !m_chatMembers.contains( handle ) ) + m_chatMembers.append( handle ); + + QString screenname = unescape(data.section( ' ', 3, 3)); + emit userJoined( handle, screenname, true ); + } + else if( cmd == "USR" ) + { + slotInviteContact(m_msgHandle); + } + else if( cmd == "BYE" ) + { + // some has disconnect from chat, update user in chat list + cleanQueue(); //in case some message are waiting their emoticons, never mind, send them + + QString handle = data.section( ' ', 0, 0 ).replace( "\r\n" , "" ); + userLeftChat( handle, (data.section( ' ', 1, 1 ) == "1" ) ? i18n("timeout") : QString::null ); + } + else if( cmd == "MSG" ) + { + QString len = data.section( ' ', 2, 2 ); + + // we need to know who's sending is the block... + m_msgHandle = data.section( ' ', 0, 0 ); + + /*//This is WRONG! the displayName is never updated on the switchboeardsocket + //so we can't trust it. + //that's why the official client does not uptade alaws the nickname immediately. + if(m_account->contacts()[ m_msgHandle ]) + { + QString displayName=data.section( ' ', 1, 1 ); + if(m_account->contacts()[ m_msgHandle ]->displayName() != displayName) + m_account->contacts()[ m_msgHandle ]->rename(displayName); + }*/ + + readBlock(len.toUInt()); + } +} + +void MSNSwitchBoardSocket::slotReadMessage( const QByteArray &bytes ) +{ + QString msg = QString::fromUtf8(bytes, bytes.size()); + + QRegExp rx("Content-Type: ([A-Za-z0-9/\\-]*)"); + rx.search(msg); + QString type=rx.cap(1); + + rx=QRegExp("User-Agent: ([A-Za-z0-9./\\-]*)"); + rx.search(msg); + QString clientStr=rx.cap(1); + + if( !clientStr.isNull() && !m_msgHandle.isNull()) + { + Kopete::Contact *c=m_account->contacts()[m_msgHandle]; + if(c) + c->setProperty( MSNProtocol::protocol()->propClient , clientStr ); + } + + // incoming message for File-transfer + if( type== "text/x-msmsgsinvite" ) + { + emit invitation(m_msgHandle,msg); + } + else if( type== "text/x-msmsgscontrol" ) + { + QString message; + message = msg.right( msg.length() - msg.findRev( " " ) - 1 ); + message = message.replace( "\r\n" ,"" ); + emit receivedTypingMsg( message.lower(), true ); + } + else if(type == "text/x-msnmsgr-datacast") + { + if(msg.contains("ID:")) + { + QRegExp rx("ID: ([0-9]*)"); + rx.search(msg); + uint dataCastId = rx.cap(1).toUInt(); + if( dataCastId == 1 ) + { + kdDebug(14140) << k_funcinfo << "Received a nudge !" << endl; + emit nudgeReceived(m_msgHandle); + } + } + } + else if(type=="text/plain" /* || type.isEmpty()*/ ) + { + // Some MSN Clients (like CCMSN) don't like to stick to the rules. + // In case of CCMSN, it doesn't send what the content type is when + // sending a text message. So if it's not supplied, we'll just + // assume its that. + + QColor fontColor; + QFont font; + + if ( msg.contains( "X-MMS-IM-Format" ) ) + { + QString fontName; + QString fontInfo; + QString color; + + rx=QRegExp("X-MMS-IM-Format: ([^\r\n]*)"); + rx.search(msg); + fontInfo =rx.cap(1); + + color = parseFontAttr(fontInfo, "CO"); + + // FIXME: this is so BAAAAAAAAAAAAD :( + if (!color.isEmpty() && color.toInt(0,16)!=0) + { + if ( color.length() == 2) // only #RR (red color) given + fontColor.setRgb( + color.mid(0,2).toInt(0,16), + 0, + 0); + else if ( color.length() == 4) // #GGRR (green, red) given. + { + fontColor.setRgb( + color.mid(2,2).toInt(0,16), + color.mid(0,2).toInt(0,16), + 0); + } + else if ( color.length() == 6) // full #BBGGRR given + { + fontColor.setRgb( + color.mid(4,2).toInt(0, 16), + color.mid(2,2).toInt(0,16), + color.mid(0,2).toInt(0,16)); + } + } + + fontName = parseFontAttr(fontInfo, "FN").replace( "%20" , " " ); + + // Some clients like Trillian and Kopete itself send a font + // name of 'MS Serif' since MS changed the server to + // _require_ a font name specified in june 2002. + // MSN's own client defaults to 'MS Sans Serif', which also + // has issues. + // Handle 'MS Serif' and 'MS Sans Serif' as an empty font name + if( !fontName.isEmpty() && fontName != "MS Serif" && fontName != "MS Sans Serif" ) + { + QString ef=parseFontAttr( fontInfo, "EF" ); + + font = QFont( fontName, + parseFontAttr( fontInfo, "PF" ).toInt(), // font size + ef.contains( 'B' ) ? QFont::Bold : QFont::Normal, + ef.contains( 'I' ) ); + font.setUnderline(ef.contains( 'U' )); + font.setStrikeOut(ef.contains( 'S' )); + } + } + + QPtrList<Kopete::Contact> others; + others.append( m_account->myself() ); + QStringList::iterator it2; + for( it2 = m_chatMembers.begin(); it2 != m_chatMembers.end(); ++it2 ) + { + if( *it2 != m_msgHandle ) + others.append( m_account->contacts()[ *it2 ] ); + } + + QString message=msg.right( msg.length() - msg.find("\r\n\r\n") - 4 ); + + //Stupid MSN PLUS colors code. message with incorrect charactere are not showed correctly in the chatwindow. + //TODO: parse theses one to show the color too in Kopete + message.replace("\3","").replace("\4","").replace("\2","").replace("\5","").replace("\6","").replace("\7",""); + + if(!m_account->contacts()[m_msgHandle]) + { + //this may happens if the contact has been deleted. + kdDebug(14140) << k_funcinfo <<"WARNING: contact is null, adding it" <<endl; + if( !m_chatMembers.contains( m_msgHandle ) ) + m_chatMembers.append( m_msgHandle ); + emit userJoined( m_msgHandle , m_msgHandle , false); + } + + Kopete::Message kmsg( m_account->contacts()[ m_msgHandle ], others, + message, + Kopete::Message::Inbound , Kopete::Message::PlainText ); + + kmsg.setFg( fontColor ); + kmsg.setFont( font ); + + rx=QRegExp("Chunks: ([0-9]*)"); + rx.search(msg); + unsigned int chunks=rx.cap(1).toUInt(); + rx=QRegExp("Chunk: ([0-9]*)"); + rx.search(msg); + unsigned int chunk=rx.cap(1).toUInt(); + + if(chunk != 0 && !m_msgQueue.isEmpty()) + { + QString msg=m_msgQueue.last().plainBody(); + m_msgQueue.pop_back(); //removes the last item + kmsg.setBody( msg+ message, Kopete::Message::PlainText ); + } + + if(chunk == 0 ) + m_chunks=chunks; + else if(chunk+1 >= m_chunks) + m_chunks=0; + + if ( m_recvIcons > 0 || m_chunks > 0) + { //Some custom emoticons are waiting to be received. so append the message to the queue + //Or the message has not been fully received, so same thing + kdDebug(14140) << k_funcinfo << "Message not fully received => append to queue. Emoticon left: " << m_recvIcons << " chunks: " << chunk+1 << " of " << m_chunks <<endl; + m_msgQueue.append( kmsg ); + if(!m_emoticonTimer) //to be sure no message will be lost, we will appends message to + { // the queue in 15 secondes even if we have not received emoticons + m_emoticonTimer=new QTimer(this); + QObject::connect(m_emoticonTimer , SIGNAL(timeout()) , this, SLOT(cleanQueue())); + m_emoticonTimer->start( 15000 , true ); + } + } + else + emit msgReceived( parseCustomEmoticons( kmsg ) ); + + } + else if( type== "text/x-mms-emoticon" || type== "text/x-mms-animemoticon") + { + // TODO remove Displatcher. + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if ( config->readBoolEntry( "useCustomEmoticons", true ) ) + { + QRegExp rx("([^\\s]*)[\\s]*(<msnobj [^>]*>)"); + rx.setMinimal(true); + int pos = rx.search(msg); + while( pos != -1) + { + QString msnobj=rx.cap(2); + QString txt=rx.cap(1); + kdDebug(14140) << k_funcinfo << "emoticon: " << txt << " msnobj: " << msnobj<< endl; + + if( !m_emoticons.contains(msnobj) || !m_emoticons[msnobj].second ) + { + m_emoticons.insert(msnobj, qMakePair(txt,(KTempFile*)0L)); + MSNContact *c=static_cast<MSNContact*>(m_account->contacts()[m_msgHandle]); + if(!c) + return; + + // we are receiving emoticons, so delay message display until received signal + m_recvIcons++; + PeerDispatcher()->requestDisplayIcon(m_msgHandle, msnobj); + } + pos=rx.search(msg, pos+rx.matchedLength()); + } + } + } + else if( type== "application/x-msnmsgrp2p" ) + { + PeerDispatcher()->slotReadMessage(m_msgHandle, bytes); + } + else if( type == "text/x-clientcaps" ) + { + rx=QRegExp("Client-Name: ([A-Za-z0-9.$!*/% \\-]*)"); + rx.search(msg); + clientStr=unescape( rx.cap(1) ); + + if( !clientStr.isNull() && !m_msgHandle.isNull()) + { + Kopete::Contact *c=m_account->contacts()[m_msgHandle]; + if(c) + c->setProperty( MSNProtocol::protocol()->propClient , clientStr ); + } + + if(!m_clientcapsSent) + { + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + + QString JabberID; + if(config->readBoolEntry("SendJabber", true)) + JabberID=config->readEntry("JabberAccount"); + + if(!JabberID.isEmpty()) + JabberID="JabberID: "+JabberID +"\r\n"; + + if( config->readBoolEntry("SendClientInfo", true) || !JabberID.isEmpty()) + { + + QCString message = QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-clientcaps\r\n" + "Client-Name: Kopete/"+escape(kapp->aboutData()->version())+"\r\n" + +JabberID+ + "\r\n" ).utf8(); + + QString args = "U"; + sendCommand( "MSG", args, true, message ); + } + m_clientcapsSent=true; + } + + + } + else if(type == "image/gif" || msg.contains("Message-ID:")) + { + // Incoming inkformatgif. + QRegExp regex("Message-ID: \\{([0-9A-F\\-]*)\\}"); + regex.search(msg); + QString messageId = regex.cap(1); + regex = QRegExp("Chunks: (\\d+)"); + regex.search(msg); + QString chunks = regex.cap(1); + regex = QRegExp("Chunk: (\\d+)"); + regex.search(msg); + QString chunk = regex.cap(1); + + if(!messageId.isNull()) + { + bool valid = true; + // Retrieve the nmber of data chunks. + Q_UINT32 numberOfChunks = chunks.toUInt(&valid); + if(valid && (numberOfChunks > 1)) + { + regex = QRegExp("base64:([0-9a-zA-Z+/=]+)"); + regex.search(msg); + // Retrieve the first chunk of the ink format gif. + QString base64 = regex.cap(1); + // More chunks are expected, buffer the chunk received. + InkMessage inkMessage; + inkMessage.chunks = numberOfChunks; + inkMessage.data += base64; + m_inkMessageBuffer.insert(messageId, inkMessage); + } + } + else + { + // There is only one chunk of data. + regex = QRegExp("base64:([0-9a-zA-Z+/=]*)"); + regex.search(msg); + // Retrieve the base64 encoded ink data. + QString data = regex.cap(1); + DispatchInkMessage(data); + } + + if(!messageId.isNull()) + { + if(m_inkMessageBuffer.contains(messageId)) + { + if(chunks.isNull()) + { + InkMessage inkMessage = m_inkMessageBuffer[messageId]; + inkMessage.data += msg.section("\r\n\r\n", -1); + if(inkMessage.chunks == chunk.toUInt() + 1) + { + DispatchInkMessage(inkMessage.data); + // Remove the ink message from the buffer. + m_inkMessageBuffer.remove(messageId); + } + } + } + } + } + else + { + kdDebug(14140) << k_funcinfo <<" Unknown type '" << type << "' message: \n"<< msg <<endl; + } +} + +void MSNSwitchBoardSocket::DispatchInkMessage(const QString& base64String) +{ + QByteArray image; + // Convert from base64 encoded string to byte array. + KCodecs::base64Decode(base64String.utf8() , image); + KTempFile *inkImage = new KTempFile(locateLocal( "tmp", "inkformatgif-" ), ".gif"); + inkImage->setAutoDelete(true); + inkImage->file()->writeBlock(image.data(), image.size()); + inkImage->file()->close(); + + slotEmoticonReceived(inkImage , "inkformatgif"); + inkImage = 0l; +} + +void MSNSwitchBoardSocket::sendTypingMsg( bool isTyping ) +{ + if( !isTyping ) + return; + + if ( onlineStatus() != Connected || m_chatMembers.empty()) + { + //we are not yet in a chat. + //if we send that command now, we may get disconnected. + return; + } + + + QCString message = QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msmsgscontrol\r\n" + "TypingUser: " + m_myHandle + "\r\n" + "\r\n" ).utf8(); + + // Length is appended by sendCommand() + QString args = "U"; + sendCommand( "MSG", args, true, message ); +} + +// this Invites an Contact +void MSNSwitchBoardSocket::slotInviteContact(const QString &handle) +{ + m_msgHandle=handle; + sendCommand( "CAL", handle ); +} +// +// Send a custum emoticon +// +int MSNSwitchBoardSocket::sendCustomEmoticon(const QString &name, const QString &filename) +{ + QString picObj; + + //try to find it in the cache. + const QMap<QString, QString> objectList = PeerDispatcher()->objectList; + for (QMap<QString,QString>::ConstIterator it = objectList.begin(); it != objectList.end(); ++it ) + { + if(it.data() == filename) + { + picObj=it.key(); + break; + } + } + + if(picObj.isNull()) + { //if not found in the cache, generate the picture object + QFileInfo fi(filename); + // open the icon file + QFile pictFile(fi.filePath()); + if (pictFile.open(IO_ReadOnly)) { + + QByteArray ar = pictFile.readAll(); + pictFile.close(); + + QString sha1d = QString(KCodecs::base64Encode(SHA1::hash(ar))); + QString size = QString::number( pictFile.size() ); + QString all = "Creator" + m_account->accountId() + "Size" + size + "Type2Location" + fi.fileName() + "FriendlyAAA=SHA1D" + sha1d; + QString sha1c = QString(KCodecs::base64Encode(SHA1::hashString(all.utf8()))); + picObj = "<msnobj Creator=\"" + m_account->accountId() + "\" Size=\"" + size + "\" Type=\"2\" Location=\""+ fi.fileName() + "\" Friendly=\"AAA=\" SHA1D=\""+sha1d+ "\" SHA1C=\""+sha1c+"\"/>"; + + PeerDispatcher()->objectList.insert(picObj, filename); + } + else + return 0; + } + + QString msg = "MIME-Version: 1.0\r\n" + "Content-Type: text/x-mms-emoticon\r\n" + "\r\n" + + name + "\t" + picObj + "\t\r\n"; + + return sendCommand("MSG", "A", true, msg.utf8()); + +} + +// this sends a short message to the server +int MSNSwitchBoardSocket::sendMsg( const Kopete::Message &msg ) +{ + if ( onlineStatus() != Connected || m_chatMembers.empty()) + { +// m_messagesQueue.append(msg); + return -1; + } + +#if 0 //this is to test webcam + if(msg.plainBody().contains("/webcam")) + { + PeerDispatcher()->startWebcam( m_myHandle , m_msgHandle); + return -3; + } +#endif + + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + if ( config->readBoolEntry( "exportEmoticons", false ) ) + { + QMap<QString, QStringList> emap = Kopete::Emoticons::self()->emoticonAndPicList(); + + // Check the list for any custom emoticons + for (QMap<QString, QStringList>::const_iterator itr = emap.begin(); itr != emap.end(); itr++) + { + for ( QStringList::const_iterator itr2 = itr.data().constBegin(); itr2 != itr.data().constEnd(); ++itr2 ) + { + if ( msg.plainBody().contains( *itr2 ) ) + sendCustomEmoticon( *itr2, itr.key() ); + } + } + } + + if( msg.format() & Kopete::Message::RichText ) + { + QRegExp regex("^\\s*<img src=\"([^>\"]+)\"[^>]*>\\s*$"); + if(regex.search(msg.escapedBody()) != -1) + { + // FIXME why are we sending the images.. the contact should request them. + PeerDispatcher()->sendImage(regex.cap(1), m_msgHandle); + return -3; + } + } + + // User-Agent is not a official flag, but GAIM has it + QString UA; + if( config->readBoolEntry("SendClientInfo", true) ) + { + UA="User-Agent: Kopete/"+escape(kapp->aboutData()->version())+"\r\n"; + } + + QString head = + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + +UA+ + "X-MMS-IM-Format: "; + + if(msg.font() != QFont() ) + { + //It's verry strange that if the font name is bigger than 31 char, the _server_ close the socket and don't deliver the message. + // the real question is why ? my guess is that MS patched the server because a bug in their client, but that's just a guess. + // - Olivier 06-2005 + head += "FN=" + escape( msg.font().family().left(31)); + head += "; EF="; + if(msg.font().bold()) + head += "B"; + if(msg.font().italic()) + head += "I"; + if(msg.font().strikeOut()) + head += "S"; + if(msg.font().underline()) + head += "U"; + head += "; "; + } + else head+="FN=; EF=; "; + /* + * I don't know what to set by default, so i decided to set nothing. CF Bug 82734 + * (but don't forgeto to add an empty FN= and EF= , or webmessenger will break. (CF Bug 102371) ) + else head+="FN=MS%20Serif; EF=; "; + */ + + // Color support + if (msg.fg().isValid()) + { + QString colorCode = QColor(msg.fg().blue(),msg.fg().green(),msg.fg().red()).name().remove(0,1); //colors aren't sent in RGB but in BGR (O.G.) + head += "CO=" + colorCode; + } + else + { + head += "CO=0"; + } + + head += "; CS=0; PF=0"; + if (msg.plainBody().isRightToLeft()) + head += "; RL=1"; + head += "\r\n"; + + QString message= msg.plainBody().replace( "\n" , "\r\n" ); + + //-- Check if the message isn't too big, TODO: do that at the libkopete level. + int len_H=head.utf8().length(); // != head.length() because i need the size in butes and + int len_M=message.utf8().length(); // some utf8 char may be longer than one byte + if( len_H+len_M >= 1660 ) //1664 is the maximum size of messages allowed by the server + { + //We will certenly split the message in several ones. + //It's possible to made the opposite client join them, as explained in this MS Word document + //http://www.bot-depot.com/forums/index.php?act=Attach&type=post&id=35110 + + head+="Message-ID: {7B7B34E6-7A8D-44FF-926C-1799156B58"+QString::number( rand()%10)+QString::number( rand()%10)+"}\r\n"; + int len_H=head.utf8().length()+ 14; //14 is the size of "Chunks: x" + //this is the size of each part of the message (excluding the header) + int futurmessages_size=1400; //1400 is a common good size + //int futurmessages_size=1664-len_H; + + int nb=(int)ceil((float)(len_M)/(float)(futurmessages_size)); + + if(KMessageBox::warningContinueCancel(0L /* FIXME: we should try to find a parent somewere*/ , + i18n("The message you are trying to send is too long; it will be split into %1 messages.").arg(nb) , + i18n("Message too big - MSN Plugin" ), KStdGuiItem::cont() , "SendLongMessages" ) + == KMessageBox::Continue ) + { + int place=0; + int result; + int chunk=0; + do + { + QString m=message.mid(place, futurmessages_size); + place += futurmessages_size; + + //make sure the size is not too big because of utf8 + int d=m.utf8().length() + len_H -1664; + if( d > 0 ) + {//it contains some utf8 chars, so we strip the string a bit. + m=m.left( futurmessages_size - d ); + place -= d; + } + + //try to snip on space if possible + int len=m.length(); + d=0; + while(d<200 && !m[len-d].isSpace() ) + d++; + if(d<200) + { + m=m.left(len-d); + place -= d; + } + QString chunk_str; + if(chunk==0) + chunk_str="Chunks: "+QString::number(nb)+"\r\n"; + else if(chunk<nb) + chunk_str="Chunk: "+QString::number(chunk)+"\r\n"; + else + { + kdDebug(14140) << k_funcinfo <<"The message is slit in more than initially estimated" <<endl; + } + result=sendCommand( "MSG", "A", true, (head+chunk_str+"\r\n"+m).utf8() ); + chunk++; + } + while(place < len_M) ; + + while(chunk<nb) + { + kdDebug(14140) << k_funcinfo <<"The message is plit in less than initially estimated. Sending empty message to complete" <<endl; + QString chunk_str="Chunk: "+QString::number(chunk); + sendCommand( "MSG", "A", true, (head+chunk_str+"\r\n").utf8() ); + chunk++; + } + return result; + } + return -2; //the message hasn't been sent. + } + + if(!m_keepAlive) + { + m_keepAliveNb=20; + m_keepAlive=new QTimer(this); + QObject::connect(m_keepAlive, SIGNAL(timeout()) , this , SLOT(slotKeepAliveTimer())); + m_keepAlive->start(50*1000); + } + + + return sendCommand( "MSG", "A", true, (head+"\r\n"+message).utf8() ); +} + +void MSNSwitchBoardSocket::slotSocketClosed( ) +{ + for( QStringList::Iterator it = m_chatMembers.begin(); it != m_chatMembers.end(); ++it ) + { + emit userLeft( (*it), i18n("connection closed")); + } + + // we have lost the connection, send a message to chatwindow (this will not displayed) +// emit switchBoardIsActive(false); + emit switchBoardClosed( ); +} + +void MSNSwitchBoardSocket::slotCloseSession() +{ + sendCommand( "OUT", QString::null, false ); + disconnect(); +} + +// Check if we are connected. If so, then send the handshake. +void MSNSwitchBoardSocket::slotOnlineStatusChanged( MSNSocket::OnlineStatus status ) +{ + if (status == Connected) + { + QCString command; + QString args; + + if( !m_ID ) // we're inviting + { + command = "USR"; + args = m_myHandle + " " + m_auth; + } + else // we're invited + { + command = "ANS"; + args = m_myHandle + " " + m_auth + " " + m_ID; + } + sendCommand( command, args ); + + if(!m_keepAlive) + { + m_keepAliveNb=20; + m_keepAlive=new QTimer(this); + QObject::connect(m_keepAlive, SIGNAL(timeout()) , this , SLOT(slotKeepAliveTimer())); + m_keepAlive->start(50*1000); + } + } +} + +void MSNSwitchBoardSocket::userLeftChat(const QString& handle , const QString &reason) +{ + emit userLeft( handle, reason ); + + if( m_chatMembers.contains( handle ) ) + m_chatMembers.remove( handle ); + + if(m_chatMembers.isEmpty()) + disconnect(); +} + +void MSNSwitchBoardSocket::requestDisplayPicture() +{ + MSNContact *contact = static_cast<MSNContact*>(m_account->contacts()[m_msgHandle]); + if(!contact) return; + + PeerDispatcher()->requestDisplayIcon(m_msgHandle, contact->object()); +} + +void MSNSwitchBoardSocket::slotEmoticonReceived( KTempFile *file, const QString &msnObj ) +{ + kdDebug(14141) << k_funcinfo << msnObj << endl; + + if(m_emoticons.contains(msnObj)) + { //it's an emoticon + m_emoticons[msnObj].second=file; + + if( m_recvIcons > 0 ) + m_recvIcons--; + kdDebug(14140) << k_funcinfo << "emoticons received queue is now: " << m_recvIcons << endl; + + if ( m_recvIcons <= 0 ) + cleanQueue(); + } + else if(msnObj == "inkformatgif") + { + QString msg=i18n("<img src=\"%1\" alt=\"Typewrited message\" />" ).arg( file->name() ); + + kdDebug(14140) << k_funcinfo << file->name() <<endl; + + m_typewrited.append(file); + m_typewrited.setAutoDelete(true); + + QPtrList<Kopete::Contact> others; + others.append( m_account->myself() ); + + QStringList::iterator it2; + for( it2 = m_chatMembers.begin(); it2 != m_chatMembers.end(); ++it2 ) + { + if( *it2 != m_msgHandle ) + others.append( m_account->contacts()[ *it2 ] ); + } + + if(!m_account->contacts()[m_msgHandle]) + { + //this may happens if the contact has been deleted. + kdDebug(14140) << k_funcinfo <<"WARNING: contact is null, adding it" <<endl; + if( !m_chatMembers.contains( m_msgHandle ) ) + m_chatMembers.append( m_msgHandle ); + emit userJoined( m_msgHandle , m_msgHandle , false); + } + + Kopete::Message kmsg( m_account->contacts()[ m_msgHandle ], others, + msg, Kopete::Message::Inbound , Kopete::Message::RichText ); + + emit msgReceived( kmsg ); + } + else //if it is not an emoticon, + { // it's certenly the displaypicture. + MSNContact *c=static_cast<MSNContact*>(m_account->contacts()[m_msgHandle]); + if(c && c->object()==msnObj) + c->setDisplayPicture(file); + else + delete file; + } +} + +void MSNSwitchBoardSocket::slotIncomingFileTransfer(const QString& from, const QString& /*fileName*/, Q_INT64 /*fileSize*/) +{ + QPtrList<Kopete::Contact> others; + others.append( m_account->myself() ); + QStringList::iterator it2; + for( it2 = m_chatMembers.begin(); it2 != m_chatMembers.end(); ++it2 ) + { + if( *it2 != m_msgHandle ) + others.append( m_account->contacts()[ *it2 ] ); + } + + if(!m_account->contacts()[m_msgHandle]) + { + //this may happens if the contact has been deleted. + kdDebug(14140) << k_funcinfo <<"WARNING: contact is null, adding it" <<endl; + if( !m_chatMembers.contains( m_msgHandle ) ) + m_chatMembers.append( m_msgHandle ); + emit userJoined( m_msgHandle , m_msgHandle , false); + } + QString invite = "Incoming file transfer."; + Kopete::Message msg = + Kopete::Message(m_account->contacts()[from], others, invite, Kopete::Message::Internal, Kopete::Message::PlainText); + emit msgReceived(msg); +} + +void MSNSwitchBoardSocket::cleanQueue() +{ + if(m_emoticonTimer) + { + m_emoticonTimer->stop(); + m_emoticonTimer->deleteLater(); + m_emoticonTimer=0L; + } + kdDebug(14141) << k_funcinfo << m_msgQueue.count() << endl; + + QValueList<const Kopete::Message>::Iterator it_msg; + for ( it_msg = m_msgQueue.begin(); it_msg != m_msgQueue.end(); ++it_msg ) + { + Kopete::Message kmsg = (*it_msg); + emit msgReceived( parseCustomEmoticons( kmsg ) ); + } + m_msgQueue.clear(); +} + +Kopete::Message &MSNSwitchBoardSocket::parseCustomEmoticons(Kopete::Message &kmsg) +{ + QString message=kmsg.escapedBody(); + QMap<QString , QPair<QString , KTempFile*> >::Iterator it; + for ( it = m_emoticons.begin(); it != m_emoticons.end(); ++it ) + { + QString es=QStyleSheet::escape(it.data().first); + KTempFile *f=it.data().second; + if(message.contains(es) && f) + { + QString imgPath = f->name(); + QImage iconImage(imgPath); + /* We don't use a comple algoritm (like the one in the #if) because the msn client shows + * emoticons like that. So, in that case, we show like the MSN client */ + #if 0 + QString em = QRegExp::escape( es ); + message.replace( QRegExp(QString::fromLatin1( "(^|[\\W\\s]|%1)(%2)(?!\\w)" ).arg(em).arg(em)), + QString::fromLatin1("\\1<img align=\"center\" width=\"") + + #endif + //match any occurence which is not in a html tag. + message.replace( QRegExp(QString::fromLatin1("%1(?![^><]*>)").arg(QRegExp::escape(es))), + QString::fromLatin1("<img align=\"center\" width=\"") + + QString::number(iconImage.width()) + + QString::fromLatin1("\" height=\"") + + QString::number(iconImage.height()) + + QString::fromLatin1("\" src=\"") + imgPath + + QString::fromLatin1("\" title=\"") + es + + QString::fromLatin1("\" alt=\"") + es + + QString::fromLatin1( "\"/>" ) ); + kmsg.setBody(message, Kopete::Message::RichText); + } + } + return kmsg; +} + +int MSNSwitchBoardSocket::sendNudge() +{ + QCString message = QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-msnmsgr-datacast\r\n" + "\r\n" + "ID: 1\r\n" + "\r\n\r\n" ).utf8(); + + QString args = "U"; + return sendCommand( "MSG", args, true, message ); +} + + + +// FIXME: This is nasty... replace with a regexp or so. +QString MSNSwitchBoardSocket::parseFontAttr(QString str, QString attr) +{ + QString tmp; + int pos1=0, pos2=0; + + pos1 = str.find(attr + "="); + + if (pos1 == -1) + return ""; + + pos2 = str.find(";", pos1+3); + + if (pos2 == -1) + tmp = str.mid(pos1+3, str.length() - pos1 - 3); + else + tmp = str.mid(pos1+3, pos2 - pos1 - 3); + + return tmp; +} + +Dispatcher* MSNSwitchBoardSocket::PeerDispatcher() +{ + if(!m_dispatcher) + { + // Create a new msnslp dispatcher to handle + // all peer to peer requests. + QStringList ip; + if(m_account->notifySocket()) + { + ip << m_account->notifySocket()->localIP(); + if(m_account->notifySocket()->localIP() != m_account->notifySocket()->getLocalIP()) + ip << m_account->notifySocket()->getLocalIP(); + } + m_dispatcher = new Dispatcher(this, m_account->accountId(),ip ); + +// QObject::connect(this, SIGNAL(blockRead(const QByteArray&)), m_dispatcher, SLOT(slotReadMessage(const QByteArray&))); +// QObject::connect(m_dispatcher, SIGNAL(sendCommand(const QString&, const QString&, bool, const QByteArray&, bool)), this, SLOT(sendCommand(const QString&, const QString&, bool, const QByteArray&, bool))); + QObject::connect(m_dispatcher, SIGNAL(incomingTransfer(const QString&, const QString&, Q_INT64)), this, SLOT(slotIncomingFileTransfer(const QString&, const QString&, Q_INT64))); + QObject::connect(m_dispatcher, SIGNAL(displayIconReceived(KTempFile *, const QString&)), this, SLOT(slotEmoticonReceived( KTempFile *, const QString&))); + QObject::connect(this, SIGNAL(msgAcknowledgement(unsigned int, bool)), m_dispatcher, SLOT(messageAcknowledged(unsigned int, bool))); + m_dispatcher->m_pictureUrl = m_account->pictureUrl(); + } + return m_dispatcher; +} + +void MSNSwitchBoardSocket::slotKeepAliveTimer( ) +{ + /* + This is a workaround against the bug 113425 + The problem: the P2P::Webcam class is parent of us, and when we get deleted, it get deleted. + the correct solution would be to change that. + The second problem: after one minute of inactivity, the official client close the chat socket. + the workaround: we simulate the activity by sending small packet each 50 seconds + the nice side effect: the "xxx has closed the chat" is now meaningfull + the bad side effect: some switchboard connection may be maintained for really long time! + */ + + if ( onlineStatus() != Connected || m_chatMembers.empty()) + { + //we are not yet in a chat. + //if we send that command now, we may get disconnected. + return; + } + + + QCString message = QString( "MIME-Version: 1.0\r\n" + "Content-Type: text/x-keepalive\r\n" + "\r\n" ).utf8(); + + // Length is appended by sendCommand() + QString args = "U"; + sendCommand( "MSG", args, true, message ); + + m_keepAliveNb--; + if(m_keepAliveNb <= 0) + { + m_keepAlive->deleteLater(); + m_keepAlive=0L; + } +} + +#include "msnswitchboardsocket.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/msnswitchboardsocket.h b/kopete/protocols/msn/msnswitchboardsocket.h new file mode 100644 index 00000000..5a6f9628 --- /dev/null +++ b/kopete/protocols/msn/msnswitchboardsocket.h @@ -0,0 +1,166 @@ +/* + msnswitchboardsocket.h - switch board connection socket + + Copyright (c) 2002 by Martijn Klingens <klingens@kde.org> + Copyright (c) 2002-2006 by Olivier Goffart <ogoffart@ kde.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + Portions of this code are taken from KMerlin, + (c) 2001 by Olaf Lueg <olueg@olsd.de> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNSWITCHBOARDSOCKET_H +#define MSNSWITCHBOARDSOCKET_H + +#include <qobject.h> +#include <qstrlist.h> +#include <qvaluevector.h> + +#include <kstringhandler.h> + +#include "msnsocket.h" + +namespace Kopete { class Message; } +class MSNAccount; +class QTimer; + +class MSNP2PDisplatcher; +class KTempFile; + +namespace P2P { class Dispatcher; } + +#include "dispatcher.h" + +class KOPETE_EXPORT MSNSwitchBoardSocket : public MSNSocket +{ + Q_OBJECT + +public: + /** + * Contructor: id is the KopeteMessageMangager's id + */ + MSNSwitchBoardSocket( MSNAccount * account , QObject *parent); + ~MSNSwitchBoardSocket(); + +private: + P2P::Dispatcher *m_dispatcher; + MSNAccount *m_account; + + QString m_myHandle; // our handle + + // contains the handle of the last person that msg'ed us. + // since we receive the actual message by readBlock(), we need + // to remember what the handle was of the person sending us the message. + QString m_msgHandle; + + QString m_ID; + QString m_auth; + QStringList m_chatMembers; + + //used for emoticons + QValueList<const Kopete::Message> m_msgQueue; + unsigned m_recvIcons; + QMap<QString , QPair<QString , KTempFile*> > m_emoticons; + Kopete::Message &parseCustomEmoticons(Kopete::Message &msg); + QTimer *m_emoticonTimer; + QPtrList<KTempFile> m_typewrited; + + struct InkMessage{ + Q_UINT32 chunks; + QString data; + }; + QMap<QString, InkMessage> m_inkMessageBuffer; + + /** the number of chunk for currents messages */ + unsigned int m_chunks; + + /** true is we already sent the x-clientcaps message */ + bool m_clientcapsSent; + +private: + void DispatchInkMessage(const QString &base64String); + +protected: + /** + * Handle an MSN command response line. + */ + virtual void parseCommand( const QString &cmd, uint id, + const QString &data ); + + /** + * Handle exceptions that might occur during a chat. + */ + virtual void handleError( uint code, uint id ); + + QString parseFontAttr( QString str, QString attr ); + + +public: + void connectToSwitchBoard( QString ID, QString address, QString auth ); + void setHandle( QString handle ) { m_myHandle = handle; } + void setMsgHandle( QString handle ) { m_msgHandle = handle; } + + const QStringList &chatMembers() { return m_chatMembers; } + + void userLeftChat( const QString &handle , const QString &reason ); + int sendMsg( const Kopete::Message &msg ); + int sendCustomEmoticon(const QString &name, const QString &filename); + + int sendNudge(); + + P2P::Dispatcher* PeerDispatcher(); + +public slots: + void slotCloseSession(); + void slotInviteContact(const QString &handle); + + /** + * Notify the server that the user is typing a message + */ + void sendTypingMsg( bool isTyping ); + + void requestDisplayPicture(); + + /** workaround Bug 113425 . see slotKeepAliveTimer() **/ + QTimer *m_keepAlive; + int m_keepAliveNb; + + + +private slots: + void slotOnlineStatusChanged( MSNSocket::OnlineStatus status ); + void slotSocketClosed( ); + void slotReadMessage( const QByteArray &bytes ); + void slotEmoticonReceived( KTempFile *, const QString& ); + void slotIncomingFileTransfer(const QString& from, const QString& fileName, Q_INT64 fileSize); + void cleanQueue(); + + /** workaround Bug 113425 . see comment inside the function **/ + void slotKeepAliveTimer(); + +signals: + void msgReceived( Kopete::Message &msg ); + void receivedTypingMsg( const QString &contactId, bool isTyping ); + void msgAcknowledgement(unsigned int, bool); + void userJoined(const QString& handle , const QString &publicName , bool IRO); + void userLeft(const QString& handle , const QString &reason); + void nudgeReceived(const QString &handle); + + void switchBoardClosed( ); + void invitation(const QString& handle, const QString& msg); + +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/outgoingtransfer.cpp b/kopete/protocols/msn/outgoingtransfer.cpp new file mode 100644 index 00000000..4879cf52 --- /dev/null +++ b/kopete/protocols/msn/outgoingtransfer.cpp @@ -0,0 +1,432 @@ +/* + outgoingtransfer.cpp - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "outgoingtransfer.h" + +#include <stdlib.h> + +// Kde includes +#include <kbufferedsocket.h> +#include <kdebug.h> +#include <klocale.h> +#include <kmdcodec.h> +using namespace KNetwork; + +// Qt includes +#include <qfile.h> +#include <qregexp.h> +#include <qtimer.h> + +// Kopete includes +#include <kopetetransfermanager.h> + +#include <netinet/in.h> // For htonl +using P2P::TransferContext; +using P2P::Dispatcher; +using P2P::OutgoingTransfer; +using P2P::Message; + +OutgoingTransfer::OutgoingTransfer(const QString& to, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId) +: TransferContext(to,dispatcher,sessionId) +{ + m_direction = Outgoing; + m_handshake = 0x01; +} + +OutgoingTransfer::~OutgoingTransfer() +{ + kdDebug(14140) << k_funcinfo << endl; +} + +void OutgoingTransfer::sendImage(const QByteArray& image) +{ + +// TODO QByteArray base64 = KCodecs::base64Encode(image); +// +// QCString body = "MIME-Version: 1.0\r\n" +// "Content-Type: image/gif\r\n" +// "\r\n" +// "base64:" + +// +// Message outbound; +// outbound.header.sessionId = m_sessionId; +// outbound.header.identifier = m_baseIdentifier; +// outbound.header.dataOffset = 0; +// outbound.header.totalDataSize = 4; +// outbound.header.dataSize = 4; +// outbound.header.flag = 0; +// outbound.header.ackSessionIdentifier = rand()%0x8FFFFFF0 + 4; +// outbound.header.ackUniqueIdentifier = 0; +// outbound.header.ackDataSize = 0l; +// QByteArray bytes(4); +// bytes.fill('\0'); +// outbound.body = bytes; +// outbound.applicationIdentifier = 0; +// outbound.attachApplicationId = false; +// outbound.destination = m_recipient; +// +// sendMessage(outbound, body); +} + +void OutgoingTransfer::slotSendData() +{ + Q_INT32 bytesRead = 0; + QByteArray buffer(1202); + if(!m_file) + return; + + // Read a chunk from the source file. + bytesRead = m_file->readBlock(buffer.data(), buffer.size()); + + if (bytesRead < 0) { + m_file->close(); + // ### error handling + } + else { + + if(bytesRead < 1202){ + buffer.resize(bytesRead); + } + + kdDebug(14140) << k_funcinfo << QString("Sending, %1 bytes").arg(bytesRead) << endl; + + if((m_offset + bytesRead) < m_file->size()) + { + sendData(buffer); + m_offset += bytesRead; + } + else + { + m_isComplete = true; + // Send the last chunk of the file. + sendData(buffer); + m_offset += buffer.size(); + // Close the file. + m_file->close(); + } + } + + if(m_transfer){ + m_transfer->slotProcessed(m_offset); + if(m_isComplete){ + // The transfer is complete. + m_transfer->slotComplete(); + } + } +} + +void OutgoingTransfer::acknowledged() +{ + kdDebug(14140) << k_funcinfo << endl; + + switch(m_state) + { + case Invitation: + { + if(m_type == UserDisplayIcon) + { + m_state = Negotiation; + // Send data preparation message. + sendDataPreparation(); + } + break; + } + + case Negotiation: + { + if(m_type == UserDisplayIcon) + { + // <<< Data preparation acknowledge message. + m_state = DataTransfer; + m_identifier++; + // Start sending data. + slotSendData(); + } + break; + } + + case DataTransfer: + // NOTE <<< Data acknowledged message. + // <<< Bye message should follow. + if(m_type == File) + { + if(m_handshake == 0x01) + { + // Data handshake acknowledge message. + // Start sending data. + slotSendData(); + } + else if(m_handshake == 0x02) + { + // Data acknowledge message. + // Send the recipient a BYE message. + m_state = Finished; + sendMessage(BYE, "\r\n"); + } + } + + break; + + case Finished: + if(m_type == File) + { + // BYE acknowledge message. + m_dispatcher->detach(this); + } + + break; + } +} + +void OutgoingTransfer::processMessage(const Message& message) +{ + QString body = + QCString(message.body.data(), message.header.dataSize); + kdDebug(14140) << k_funcinfo << "received, " << body << endl; + + if(body.startsWith("BYE")) + { + m_state = Finished; + // Send the recipient an acknowledge message. + acknowledge(message); + if(!m_isComplete) + { + // The peer cancelled the transfer. + if(m_transfer) + { + // Inform the user of the file transfer cancelation. + m_transfer->slotError(KIO::ERR_ABORTED, i18n("File transfer canceled.")); + } + } + // Dispose of this transfer context. + m_dispatcher->detach(this); + } + else if(body.startsWith("MSNSLP/1.0 200 OK")) + { + // Retrieve the message content type. + QRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); + regex.search(body); + QString contentType = regex.cap(1); + + if(contentType == "application/x-msnmsgr-sessionreqbody") + { + // Recipient has accepted the file transfer. + // Acknowledge the recipient. + acknowledge(message); + + // Try to open the file for reading. + // If an error occurs, send an internal + // error message to the recipient. + if(!m_file->open(IO_ReadOnly)){ + error(); + return; + } + + // Retrieve the receiving client's contact. + Kopete::Contact *contact = m_dispatcher->getContactByAccountId(m_recipient); + if(contact == 0l) + { + error(); + return; + } + + m_transfer = + Kopete::TransferManager::transferManager()->addTransfer(contact, m_file->name(), m_file->size(), m_recipient, Kopete::FileTransferInfo::Outgoing); + + QObject::connect(m_transfer , SIGNAL(transferCanceled()), this, SLOT(abort())); + + m_state = Negotiation; + + m_branch = P2P::Uid::createUid(); + + // Send the direct connection invitation message. + QString content = "Bridges: TRUDPv1 TCPv1\r\n" + + QString("NetID: %1\r\n").arg("-123657987") + + QString("Conn-Type: %1\r\n").arg("Restrict-NAT") + + "UPnPNat: false\r\n" + "ICF: false\r\n" + + QString("Hashed-Nonce: {%1}\r\n").arg(P2P::Uid::createUid()) + + "\r\n"; + sendMessage(INVITE, content); + } + else if(contentType == "application/x-msnmsgr-transrespbody") + { + // Determine whether the recipient created + // a listening endpoint. + regex = QRegExp("Listening: ([^\r\n]+)\r\n"); + regex.search(body); + bool isListening = (regex.cap(1) == "true"); + + // Send the recipient an acknowledge message. + acknowledge(message); + + m_state = DataTransfer; + +#if 1 + isListening = false; // TODO complete direct connection. +#endif + if(isListening) + { + // Retrieve the hashed nonce for this direct connection instance. + regex = QRegExp("Hashed-Nonce: \\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + m_nonce = regex.cap(1); + // Retrieve the listening endpoints of the receiving client. + regex = QRegExp("IPv4Internal-Addrs: ([^\r\n]+)\r\n"); + regex.search(body); + m_peerEndpoints = QStringList::split(" ", regex.cap(1)); + m_endpointIterator = m_peerEndpoints.begin(); + // Retrieve the listening port of the receiving client. + regex = QRegExp("IPv4Internal-Port: ([^\r\n]+)\r\n"); + regex.search(body); + m_remotePort = regex.cap(1); + + // Try to connect to the receiving client's + // listening endpoint. + connectToEndpoint(*m_endpointIterator); + } + else + { + m_handshake = 0x02; + // Otherwise, send data through the already + // existing session. + slotSendData(); + } + } + } + else if(body.startsWith("MSNSLP/1.0 603 Decline")) + { + // File transfer has been cancelled remotely. + // Send an acknowledge message + acknowledge(message); + if(m_transfer) + { + // Inform the user of the file transfer cancelation. + m_transfer->slotError(KIO::ERR_ABORTED, i18n("File transfer canceled.")); + } + + if(m_file && m_file->isOpen()){ + // Close the file. + m_file->close(); + } + m_dispatcher->detach(this); + } +} + +void OutgoingTransfer::readyToSend() +{ + if(m_isComplete){ + // Ignore, do nothing. + return; + } + + slotSendData(); +} + +void OutgoingTransfer::connectToEndpoint(const QString& hostName) +{ + m_socket = new KBufferedSocket(hostName, m_remotePort); + m_socket->setBlocking(false); + m_socket->enableRead(true); + // Disable write signal for now. Only enable + // when we are ready to sent data. + // NOTE readyWrite consumes too much cpu usage. + m_socket->enableWrite(false); + QObject::connect(m_socket, SIGNAL(readyRead()), this, SLOT(slotRead())); + QObject::connect(m_socket, SIGNAL(connected(const KResolverEntry&)), this, SLOT(slotConnected())); + QObject::connect(m_socket, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); + QObject::connect(m_socket, SIGNAL(closed()), this, SLOT(slotSocketClosed())); + // Try to connect to the endpoint. + m_socket->connect(); +} + +void OutgoingTransfer::slotConnected() +{ + kdDebug(14140) << k_funcinfo << endl; + // Check if connection is ok. + Q_UINT32 bytesWritten = m_socket->writeBlock(QCString("foo").data(), 4); + if(bytesWritten != 4) + { + // Not all data was written, close the socket. + m_socket->closeNow(); + // Schedule the data to be sent through the existing session. + QTimer::singleShot(2000, this, SLOT(slotSendData())); + return; + } + + // Send data handshake message. + P2P::Message handshake; + handshake.header.sessionId = 0; + handshake.header.identifier = ++m_identifier; + handshake.header.dataOffset = 0l; + handshake.header.totalDataSize = 0l; + handshake.header.dataSize = 0; + // Set the flag to indicate that this is + // a direct connection handshake message. + handshake.header.flag = 0x100; + QString nonce = m_nonce.remove('-'); + handshake.header.ackSessionIdentifier = nonce.mid(0, 8).toUInt(0, 16); + handshake.header.ackUniqueIdentifier = + nonce.mid(8, 4).toUInt(0, 16) | (nonce.mid(12, 4).toUInt(0, 16) << 16); + const Q_UINT32 lo = nonce.mid(16, 8).toUInt(0, 16); + const Q_UINT32 hi = nonce.mid(24, 8).toUInt(0, 16); + handshake.header.ackDataSize = + ((Q_INT64)htonl(lo)) | (((Q_INT64)htonl(hi)) << 32); + + QByteArray stream; + // Write the message to the memory stream. + m_messageFormatter.writeMessage(handshake, stream, true); + // Send the byte stream over the wire. + m_socket->writeBlock(stream.data(), stream.size()); +} + +void OutgoingTransfer::slotRead() +{ + Q_INT32 bytesAvailable = m_socket->bytesAvailable(); + kdDebug(14140) << k_funcinfo << bytesAvailable << ", bytes available." << endl; +} + +void OutgoingTransfer::slotSocketError(int) +{ + kdDebug(14140) << k_funcinfo << m_socket->errorString() << endl; + // If an error has occurred, try to connect + // to another available peer endpoint. + // If there are no more available endpoints, + // send the data through the session. + m_socket->closeNow(); + + // Move to the next available endpoint. + m_endpointIterator++; + if(m_endpointIterator != m_peerEndpoints.end()){ + // Try to connect to the endpoint. + connectToEndpoint(*m_endpointIterator); + } + else + { + // Otherwise, send the data through the session. + m_identifier -= 1; + QTimer::singleShot(2000, this, SLOT(slotSendData())); + } +} + +void OutgoingTransfer::slotSocketClosed() +{ + kdDebug(14140) << k_funcinfo << endl; + m_socket->deleteLater(); + m_socket = 0l; +} + +#include "outgoingtransfer.moc" diff --git a/kopete/protocols/msn/outgoingtransfer.h b/kopete/protocols/msn/outgoingtransfer.h new file mode 100644 index 00000000..014971ef --- /dev/null +++ b/kopete/protocols/msn/outgoingtransfer.h @@ -0,0 +1,59 @@ +/* + outgoingtransfer.h - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef OUTGOINGTRANSFER_H +#define OUTGOINGTRANSFER_H + +#include "p2p.h" +#include "dispatcher.h" +#include <qstringlist.h> + +/** +@author Kopete Developers +*/ +namespace P2P{ + class OutgoingTransfer : public TransferContext + { Q_OBJECT + public: + OutgoingTransfer(const QString& to, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId); + virtual ~OutgoingTransfer(); + + void sendImage(const QByteArray& image); + + private slots: + void slotConnected(); + void slotRead(); + void slotSendData(); + void slotSocketError(int); + void slotSocketClosed(); + + private: + virtual void acknowledged(); + void connectToEndpoint(const QString& hostName); + virtual void processMessage(const Message& message); + + QStringList m_peerEndpoints; + QStringList::Iterator m_endpointIterator; + QString m_remotePort; + QString m_nonce; + char m_handshake; + + protected: + virtual void readyToSend(); + }; +} + +#endif diff --git a/kopete/protocols/msn/p2p.cpp b/kopete/protocols/msn/p2p.cpp new file mode 100644 index 00000000..219fd935 --- /dev/null +++ b/kopete/protocols/msn/p2p.cpp @@ -0,0 +1,412 @@ +/* + p2p.cpp - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "p2p.h" +#include "dispatcher.h" +using P2P::TransferContext; +using P2P::Message; +using P2P::MessageType; +using P2P::TransferType; + +#include <stdlib.h> + +// Kde includes +#include <kbufferedsocket.h> +#include <kdebug.h> +// Qt includes +#include <qfile.h> + +// Kopete includes +#include <kopetetransfermanager.h> + +QString P2P::Uid::createUid() +{ + return (QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + "-" + + QString::number(rand()%0xAAFF+0x1111, 16) + "-" + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16) + + QString::number((unsigned long int)rand()%0xAAFF+0x1111, 16)).upper(); +} + +TransferContext::TransferContext(const QString &contact, P2P::Dispatcher *dispatcher, Q_UINT32 sessionId) + : QObject(dispatcher) , + m_sessionId(sessionId) , + m_identifier(0) , + m_file(0) , + m_transactionId (0), + m_ackSessionIdentifier (0) , + m_ackUniqueIdentifier ( 0 ), + m_transfer ( 0l) , + + m_baseIdentifier(rand()%0x0FFFFFF0 + 4), + m_dispatcher (dispatcher), + m_isComplete (false) , + m_offset(0), + m_totalDataSize(0), + m_recipient(contact), + m_sender(dispatcher->localContact()), + m_socket(0), + m_state ( Invitation) +{ + m_type = File ; //uh, why??? -Olivier +} + +TransferContext::~TransferContext() +{ + m_transfer = 0l; + + if(m_file){ + delete m_file; + m_file = 0l; + } +} + +void TransferContext::acknowledge(const Message& message) +{ + kdDebug(14140) << k_funcinfo << m_dispatcher<< endl; + + Message outbound; + outbound.header.sessionId = message.header.sessionId; + + if(m_identifier == 0){ + m_identifier = m_baseIdentifier; + } +// else if(m_state == Finished && m_direction == Incoming){ +// m_identifier = m_baseIdentifier - 1; +// } + else if(m_state == Finished && m_direction == Outgoing){ + m_identifier = m_baseIdentifier + 1; + } + else + ++m_identifier; + + outbound.header.identifier = m_identifier; + outbound.header.dataOffset = 0l; + outbound.header.totalDataSize = message.header.totalDataSize; + outbound.header.dataSize = 0; +// if(m_type == UserDisplayIcon && m_state == Finished){ +// if(m_direction == Outgoing){ +// outbound.header.flag = 0x40; +// } +// } +// else + outbound.header.flag = 2; + + outbound.header.ackSessionIdentifier = message.header.identifier; + outbound.header.ackUniqueIdentifier = message.header.ackSessionIdentifier; + outbound.header.ackDataSize = message.header.totalDataSize; + // NOTE outbound.body is null by default + outbound.applicationIdentifier = 0l; + outbound.destination = m_recipient; + + QByteArray stream; + // Write the acknowledge message to the stream. + m_messageFormatter.writeMessage(outbound, stream, (m_socket != 0l)); + if(!m_socket) + { + // Send the acknowledge message. + m_dispatcher->callbackChannel()->send(stream); + } + else + { + // Send acknowledge message directly. + m_socket->writeBlock(stream.data(), stream.size()); + } +} + +void TransferContext::error() +{ + kdDebug(14140) << k_funcinfo << endl; + sendMessage(ERROR); + m_dispatcher->detach(this); +} + +void TransferContext::sendData(const QByteArray& bytes) +{ + Message outbound; + outbound.header.sessionId = m_sessionId; + outbound.header.identifier = m_identifier; + outbound.header.dataOffset = m_offset; + if(m_file){ + outbound.header.totalDataSize = m_file->size(); + } + else + outbound.header.totalDataSize = m_totalDataSize; + + outbound.header.dataSize = bytes.size(); + if(m_type == UserDisplayIcon){ + outbound.header.flag = 0x20; + } + else if(m_type == P2P::File){ + outbound.header.flag = 0x01000030; + } + else outbound.header.flag = 0; + + outbound.header.ackSessionIdentifier = rand()%0x8FFFFFF0 + 4; + outbound.header.ackUniqueIdentifier = 0; + outbound.header.ackDataSize = 0l; + outbound.body = bytes; + outbound.applicationIdentifier = (uint)m_type; + + outbound.destination = m_recipient; + + QByteArray stream; + m_messageFormatter.writeMessage(outbound, stream, (m_socket != 0l)); + if(!m_socket) + { + // Send the data message. + m_transactionId = m_dispatcher->callbackChannel()->send(stream); + } + else + { + // Send data directly. + m_socket->writeBlock(stream.data(), stream.size()); + } +} + +void TransferContext::sendDataPreparation() +{ + kdDebug(14140) << k_funcinfo << endl; + + Message outbound; + outbound.header.sessionId = m_sessionId; + outbound.header.identifier = ++m_identifier; + outbound.header.dataOffset = 0; + outbound.header.totalDataSize = 4; + outbound.header.dataSize = 4; + outbound.header.flag = 0; + outbound.header.ackSessionIdentifier = rand()%0x8FFFFFF0 + 4; + outbound.header.ackUniqueIdentifier = 0; + outbound.header.ackDataSize = 0l; + QByteArray bytes(4); + bytes.fill('\0'); + outbound.body = bytes; + outbound.applicationIdentifier = 1; + outbound.destination = m_recipient; + + QByteArray stream; + m_messageFormatter.writeMessage(outbound, stream); + // Send the receiving client the data prepartion message. + m_dispatcher->callbackChannel()->send(stream); +} + +void TransferContext::sendMessage(MessageType type, const QString& content, Q_INT32 flag, Q_INT32 appId) +{ + Message outbound; + if(appId != 0){ + outbound.header.sessionId = m_sessionId; + } + else + outbound.header.sessionId = 0; + + if(m_identifier == 0){ + m_identifier = m_baseIdentifier; + } + else if(m_state == Invitation && m_direction == P2P::Outgoing && m_type == UserDisplayIcon) + { + m_identifier -= 3; + } + else if(m_state == Invitation && m_direction == P2P::Incoming && m_type == File) + { + m_identifier -= 3; + } + else + ++m_identifier; + + outbound.header.identifier = m_identifier; + outbound.header.flag = flag; + outbound.header.ackSessionIdentifier = m_ackSessionIdentifier; + outbound.header.ackUniqueIdentifier = m_ackUniqueIdentifier; + outbound.header.ackDataSize = 0l; + outbound.applicationIdentifier = appId; + outbound.destination = m_recipient; + + QString contentType, cSeq, method; + + switch(m_state) + { + case DataTransfer: + contentType = "application/x-msnmsgr-transreqbody"; + if(m_type == File && m_direction == Incoming) + { + contentType = "application/x-msnmsgr-transrespbody"; + } + break; + case Finished: + contentType = "application/x-msnmsgr-sessionclosebody"; + break; + default: + contentType = "application/x-msnmsgr-sessionreqbody"; + if(m_type == File && m_direction == Outgoing) + { + if(m_state == Negotiation){ + contentType = "application/x-msnmsgr-transreqbody"; + } + } + if(m_type == P2P::WebcamType && type==P2P::INVITE && m_state == Negotiation) + { + contentType = "application/x-msnmsgr-transreqbody"; + } + break; + } + + switch(type) + { + case BYE: + method = "BYE MSNMSGR:" + m_recipient + " MSNSLP/1.0"; + cSeq = "0"; + break; + + case DECLINE: + method = "MSNSLP/1.0 603 DECLINE"; + cSeq = "1"; + break; + + case ERROR: + contentType = "null"; + method = "MSNSLP/1.0 500 Internal Error"; + cSeq = "1"; + break; + + case INVITE: + method = "INVITE MSNMSGR:" + m_recipient + " MSNSLP/1.0"; + cSeq = "0"; + break; + + case OK: + method = "MSNSLP/1.0 200 OK"; + cSeq = "1"; + break; + } + + QCString body = QString(method + "\r\n" + "To: <msnmsgr:" + m_recipient + ">\r\n" + "From: <msnmsgr:" + m_sender + ">\r\n" + "Via: MSNSLP/1.0/TLP ;branch={" + m_branch.upper() + "}\r\n" + "CSeq: "+ cSeq +"\r\n" + "Call-ID: {" + m_callId.upper() + "}\r\n" + "Max-Forwards: 0\r\n" + "Content-Type: " + contentType + "\r\n" + "Content-Length: "+ QString::number(content.length() + 1) + "\r\n" + "\r\n" + + content).utf8(); + + // NOTE The body must have a null character at the end. + // QCString by chance automatically adds a \0 to the + // end of the string. + + outbound.header.totalDataSize = body.size(); + // Send the outbound message. + sendMessage(outbound, body); +} + +void TransferContext::sendMessage(Message& outbound, const QByteArray& body) +{ + Q_INT64 offset = 0L, bytesLeft = outbound.header.totalDataSize; + Q_INT16 chunkLength = 1202; + + // Split the outbound message if necessary. + while(bytesLeft > 0L) + { + if(bytesLeft < chunkLength) + { + // Copy the last chunk of the multipart message. + outbound.body.duplicate(body.data() + offset, bytesLeft); + outbound.header.dataSize = bytesLeft; + outbound.header.dataOffset = offset; + bytesLeft = 0L; + } + else + { + // Copy the next chunk of the multipart message in the sequence. + outbound.body.duplicate(body.data() + offset, chunkLength); + outbound.header.dataSize = chunkLength; + outbound.header.dataOffset = offset; + offset += chunkLength; + bytesLeft -= offset; + } + + kdDebug(14140) << k_funcinfo << + QCString(outbound.body.data(), outbound.body.size()) + << endl; + + QByteArray stream; + // Write the outbound message to the stream. + m_messageFormatter.writeMessage(outbound, stream, (m_socket != 0l)); + if(!m_socket) + { + // Send the outbound message. + m_dispatcher->callbackChannel()->send(stream); + } + else + { + // Send outbound message directly. + m_socket->writeBlock(stream.data(), stream.size()); + } + } +} + +void TransferContext::setType(TransferType type) +{ + m_type = type; +} + +void TransferContext::abort() +{ + kdDebug(14140) << k_funcinfo << endl; + if(m_transfer) + { + if(m_transfer->error() == KIO::ERR_ABORTED) + { + switch(m_direction) + { + case P2P::Outgoing: + if(m_type == File) + { + // Do nothing. + } + break; + + case P2P::Incoming: + if(m_type == File) + { + // Do nothing. + } + break; + } + } + else + { + m_state = Finished; + sendMessage(BYE, "\r\n"); + } + } +} + +void TransferContext::readyWrite() +{ + if(m_direction == Outgoing && m_state == DataTransfer){ + readyToSend(); + } +} + +void TransferContext::readyToSend() +{} + +#include "p2p.moc" diff --git a/kopete/protocols/msn/p2p.h b/kopete/protocols/msn/p2p.h new file mode 100644 index 00000000..c9b29af1 --- /dev/null +++ b/kopete/protocols/msn/p2p.h @@ -0,0 +1,147 @@ +/* + p2p.h - msn p2p protocol + + Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org> + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef P2P_H +#define P2P_H + +// Qt includes +#include <qobject.h> +#include "messageformatter.h" + +#include "kopete_export.h" + +#include "config.h" + +namespace Kopete { class Transfer; } +namespace Kopete { struct FileTransferInfo; } +namespace P2P { class Dispatcher; } +namespace KNetwork { class KBufferedSocket; } +class QFile; +class KTempFile; + +/** +@author Kopete Developers +*/ +namespace System{ + class Guid + { + public: + ~Guid(){} + static Guid newGuid(); + QString toString(); + + private: + Guid(){} + }; +} + +namespace P2P{ + + enum TransferType { UserDisplayIcon = 1, File = 2, WebcamType=4}; + enum TransferDirection { Incoming = 1, Outgoing = 8}; + enum MessageType { BYE, OK, DECLINE, ERROR, INVITE }; + + enum CommunicationState + { + Invitation = 1, + Negotiation = 2, + DataTransfer = 8, + Finished = 16 + }; + + struct TransportHeader + { + Q_UINT32 sessionId; + Q_UINT32 identifier; + Q_INT64 dataOffset; + Q_INT64 totalDataSize; + Q_UINT32 dataSize; + Q_UINT32 flag; + Q_UINT32 ackSessionIdentifier; + Q_UINT32 ackUniqueIdentifier; + Q_INT64 ackDataSize; + }; + + struct Message + { + public: + QString mimeVersion; + QString contentType; + QString destination; + QString source; + TransportHeader header; + QByteArray body; + Q_INT32 applicationIdentifier; + bool attachApplicationIdentifier; + }; + + class KOPETE_EXPORT Uid + { + public: static QString createUid(); + }; + + class KOPETE_EXPORT TransferContext : public QObject + { Q_OBJECT + public: + virtual ~TransferContext(); + + void acknowledge(const Message& message); + virtual void acknowledged() = 0; + void error(); + virtual void processMessage(const P2P::Message& message) = 0; + void sendDataPreparation(); + void sendMessage(MessageType type, const QString& content=QString::null, Q_INT32 flag=0, Q_INT32 appId=0); + void setType(TransferType type); + + public: + Q_UINT32 m_sessionId; + Q_UINT32 m_identifier; + QFile *m_file; + Q_UINT32 m_transactionId; + Q_UINT32 m_ackSessionIdentifier; + Q_UINT32 m_ackUniqueIdentifier; + Kopete::Transfer *m_transfer; + QString m_branch; + QString m_callId; + QString m_object; + + + public slots: + void abort(); + void readyWrite(); + + protected: + TransferContext(const QString& contact, P2P::Dispatcher *dispatcher,Q_UINT32 sessionId); + void sendData(const QByteArray& bytes); + void sendMessage(P2P::Message& outbound, const QByteArray& body); + virtual void readyToSend(); + + Q_UINT32 m_baseIdentifier; + TransferDirection m_direction; + P2P::Dispatcher *m_dispatcher; + bool m_isComplete; + Q_INT64 m_offset; + Q_INT64 m_totalDataSize; + P2P::MessageFormatter m_messageFormatter; + QString m_recipient; + QString m_sender; + KNetwork::KBufferedSocket *m_socket; + CommunicationState m_state; + TransferType m_type; + }; +} + +#endif diff --git a/kopete/protocols/msn/sha1.cpp b/kopete/protocols/msn/sha1.cpp new file mode 100644 index 00000000..84ad13ad --- /dev/null +++ b/kopete/protocols/msn/sha1.cpp @@ -0,0 +1,192 @@ +/* + * sha1.cpp - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include"sha1.h" + +/**************************************************************************** + SHA1 - from a public domain implementation by Steve Reid (steve@edmweb.com) +****************************************************************************/ + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15]^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +SHA1::SHA1() +{ + int wordSize; + + qSysInfo(&wordSize, &bigEndian); +} + +unsigned long SHA1::blk0(Q_UINT32 i) +{ + if(bigEndian) + return block->l[i]; + else + return (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) | (rol(block->l[i],8)&0x00FF00FF)); +} + +// Hash a single 512-bit block. This is the core of the algorithm. +void SHA1::transform(Q_UINT32 state[5], unsigned char buffer[64]) +{ + Q_UINT32 a, b, c, d, e; + + block = (CHAR64LONG16*)buffer; + + // Copy context->state[] to working vars + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + + // 4 rounds of 20 operations each. Loop unrolled. + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + // Add the working vars back into context.state[] + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + + // Wipe variables + a = b = c = d = e = 0; +} + +// SHA1Init - Initialize new context +void SHA1::init(SHA1_CONTEXT* context) +{ + // SHA1 initialization constants + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + +// Run your data through this +void SHA1::update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len) +{ + Q_UINT32 i, j; + + j = (context->count[0] >> 3) & 63; + if((context->count[0] += len << 3) < (len << 3)) + context->count[1]++; + + context->count[1] += (len >> 29); + + if((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + +// Add padding and return the message digest +void SHA1::final(unsigned char digest[20], SHA1_CONTEXT* context) +{ + Q_UINT32 i, j; + unsigned char finalcount[8]; + + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); // Endian independent + } + update(context, (unsigned char *)"\200", 1); + while ((context->count[0] & 504) != 448) { + update(context, (unsigned char *)"\0", 1); + } + update(context, finalcount, 8); // Should cause a transform() + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + + // Wipe variables + i = j = 0; + memset(context->buffer, 0, 64); + memset(context->state, 0, 20); + memset(context->count, 0, 8); + memset(&finalcount, 0, 8); +} + +QByteArray SHA1::hash(const QByteArray &a) +{ + SHA1_CONTEXT context; + QByteArray b(20); + + SHA1 s; + s.init(&context); + s.update(&context, (unsigned char *)a.data(), (unsigned int)a.size()); + s.final((unsigned char *)b.data(), &context); + return b; +} + +QByteArray SHA1::hashString(const QCString &cs) +{ + QByteArray a(cs.length()); + memcpy(a.data(), cs.data(), a.size()); + return SHA1::hash(a); +} + +QString SHA1::digest(const QString &in) +{ + QByteArray a = SHA1::hashString(in.utf8()); + QString out; + for(int n = 0; n < (int)a.size(); ++n) { + QString str; + str.sprintf("%02x", (uchar)a[n]); + out.append(str); + } + + return out; +} diff --git a/kopete/protocols/msn/sha1.h b/kopete/protocols/msn/sha1.h new file mode 100644 index 00000000..24f31af0 --- /dev/null +++ b/kopete/protocols/msn/sha1.h @@ -0,0 +1,59 @@ +/* + * sha1.h - Secure Hash Algorithm 1 + * Copyright (C) 2003 Justin Karneges + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef CS_SHA1_H +#define CS_SHA1_H + +#include<qstring.h> + +class SHA1 +{ +public: + static QByteArray hash(const QByteArray &); + static QByteArray hashString(const QCString &); + static QString digest(const QString &); + +private: + SHA1(); + + struct SHA1_CONTEXT + { + Q_UINT32 state[5]; + Q_UINT32 count[2]; + unsigned char buffer[64]; + }; + + typedef union { + unsigned char c[64]; + Q_UINT32 l[16]; + } CHAR64LONG16; + + void transform(Q_UINT32 state[5], unsigned char buffer[64]); + void init(SHA1_CONTEXT* context); + void update(SHA1_CONTEXT* context, unsigned char* data, Q_UINT32 len); + void final(unsigned char digest[20], SHA1_CONTEXT* context); + + unsigned long blk0(Q_UINT32 i); + bool bigEndian; + + CHAR64LONG16* block; +}; + +#endif diff --git a/kopete/protocols/msn/transport.cpp b/kopete/protocols/msn/transport.cpp new file mode 100644 index 00000000..492117b6 --- /dev/null +++ b/kopete/protocols/msn/transport.cpp @@ -0,0 +1,356 @@ +/* + transport.cpp - Peer to peer transport + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#include "transport.h" +#include "messageformatter.h" + +//BEGIN QT Includes +//END + +//BEGIN KDE Includes +#include <kclientsocketbase.h> +#include <kdebug.h> +#include <kstreamsocket.h> +//END + +//BEGIN Using Directives +using namespace KNetwork; +//END + +#include "msnswitchboardsocket.h" + +namespace PeerToPeer { + +Transport::Transport(QObject* parent, const char* name) + : QObject(parent, name) +{ + mFormatter = new PeerToPeer::MessageFormatter(this); +} + + +Transport::~Transport() +{ +} + +//BEGIN Public Methods + +TransportBridge* Transport::getBridge (const QString& to, Q_UINT16 port, TransportBridgeType type, const QString& identifier) +{ + TransportBridge *bridge = 0l; + KInetSocketAddress address; + if (mAddresses.contains(to)) + { + address = mAddresses[to]; + } + else + { + address = KInetSocketAddress(KIpAddress(to), port); + mAddresses[to] = address; + } + + if (PeerToPeer::Tcp == type){ + bridge = new TcpTransportBridge(address, mFormatter, this, identifier.ascii()); + } + + if (PeerToPeer::Udp == type){ +// TODO Add class UdpTransportBridge +// bridge = new UdpTransportBridge(address, this, mFormatter, identifier.ascii()); + } + + if (bridge != 0l) + { + QObject::connect(bridge, SIGNAL(readyRead(const QByteArray&)), SLOT(slotOnReceive(const QByteArray&))); + } + + return 0l; +} + +void Transport::setDefaultBridge(MSNSwitchBoardSocket* mss) +{ + mDefaultBridge = mss; + QObject::connect((MSNSwitchBoardSocket*)mDefaultBridge, SIGNAL(messageReceived(const QString&, const QByteArray&)), SLOT(slotOnReceive(const QString&, const QByteArray&))); +} + +//END + +//BEGIN Private Slot Methods + +// void Transport::slotOnReceive(Message& message) +// { +// } + +void Transport::slotOnReceive(const QString& contact, const QByteArray& bytes) +{ + kdDebug (14140) << k_funcinfo << " >> RECEIVED " << bytes.size() << " bytes." << endl; +// Message message = mFormatter->readMessage(bytes); +} + +//END + + + + +TransportBridge::TransportBridge(const KNetwork::KInetSocketAddress& to, MessageFormatter* formatter, QObject* parent, const char* name) +: QObject(parent, name) +{ + mAddress = to; + mFormatter = formatter; +} + +TransportBridge::TransportBridge(KNetwork::KClientSocketBase* socket, MessageFormatter* formatter, QObject* parent, const char* name) +: QObject(parent, name) +{ + mSocket = socket; + mAddress = mSocket->peerAddress(); +} + +TransportBridge::~TransportBridge() +{ +} + +//BEGIN Public Methods + +void TransportBridge::connect() +{ + slotOnConnect(); +} + +void TransportBridge::disconnect() +{ + slotOnDisconnect(); +} + +//END + +//BEGIN Protected Slot Methods + +void TransportBridge::slotOnConnect() +{ +} + +void TransportBridge::slotOnDisconnect() +{ +} + +void TransportBridge::slotOnError(int) +{ +} + +void TransportBridge::slotOnSocketClose() +{ +} + +void TransportBridge::slotOnSocketConnect() +{ +} + +void TransportBridge::slotOnSocketReceive() +{ +} + + +//END + + + +TcpTransportBridge::TcpTransportBridge(const KNetwork::KInetSocketAddress& to, MessageFormatter* formatter, QObject* parent, const char* name) +: TransportBridge(to, formatter, parent, name) +{ + mSocket = new KStreamSocket(mAddress.ipAddress().toString(), QString::number(mAddress.port()), this); + mSocket->setBlocking(false); + QObject::connect(mSocket, SIGNAL(connected(const KResolverEntry&)), SLOT(slotOnSocketConnect())); + QObject::connect(mSocket, SIGNAL(gotError(int)), SLOT(slotOnError(int))); + mConnected = false; +} + +TcpTransportBridge::TcpTransportBridge(KNetwork::KClientSocketBase* socket, MessageFormatter* formatter, QObject* parent, const char* name) +: TransportBridge(socket, formatter, parent, name) +{ + mConnected = (mSocket->state() == KStreamSocket::Open) ? true : false; + mSocket->setBlocking(false); +} + +TcpTransportBridge::~TcpTransportBridge() +{ +} + +//BEGIN Protected Slot Methods + +void TcpTransportBridge::slotOnConnect() +{ + if (mConnected) + { + kdDebug(14140) << k_funcinfo << "Bridge (" << name() << ") ALREADY CONNECTED " << mSocket->peerAddress().toString() << " <-> " << mSocket->localAddress().toString() << endl; + return; + } + + KStreamSocket *socket = static_cast<KStreamSocket*>(mSocket); + socket->setTimeout(5000); + QObject::connect(socket, SIGNAL(timeOut()), SLOT(slotOnSocketConnectTimeout())); + mSocket->connect(); +} + +void TcpTransportBridge::slotOnDisconnect() +{ + if (mConnected){ + mSocket->close(); + } +} + +void TcpTransportBridge::slotOnError(int errorCode) +{ + kdDebug(14140) << k_funcinfo << "Bridge (" << name() << ") ERROR occurred on {" << mSocket->localAddress().toString() << " <-> " << mSocket->peerAddress().toString() << "} - " << mSocket->errorString() << endl; + emit bridgeError(QString("Bridge ERROR %1: %2").arg(errorCode).arg(mSocket->errorString())); + if (mConnected){ + mSocket->disconnect(); + mConnected = false; + } + mSocket->deleteLater(); + mSocket = 0l; +} + +void TcpTransportBridge::slotOnSocketClose() +{ + mSocket->disconnect(); + kdDebug(14140) << k_funcinfo << "Bridge (" << name() << ") DISCONNECTED {" << mSocket->peerAddress().toString() << " <-> " << mSocket->localAddress().toString() << "}" << endl; + mConnected = false; + mSocket->deleteLater(); + mSocket = 0l; + + emit bridgeDisconnect(); +} + +void TcpTransportBridge::slotOnSocketConnect() +{ + kdDebug(14140) << k_funcinfo << "Bridge (" << name() << ") CONNECTED to " << mSocket->peerAddress().toString() << " from " + << mSocket->localAddress().toString() << endl; + mConnected = true; + + QObject::connect(mSocket, SIGNAL(readyRead()), SLOT(slotOnSocketReceive())); + QObject::connect(mSocket, SIGNAL(closed()), SLOT(slotOnSocketClose())); + + mVerified = true; + QString foo = "foo\0"; + mSocket->writeBlock(foo.ascii(), foo.length()); + foo = QString::null; + + emit bridgeConnect(); +} + +void TcpTransportBridge::slotOnSocketReceive() +{ + kdDebug (14140) << k_funcinfo << "Bridge (" << name() << ") RECEIVED " << mSocket->bytesAvailable() << " bytes." << endl; + + QByteArray bytes(mSocket->bytesAvailable()); + mSocket->readBlock(bytes.data(), bytes.size()); + // Write the data to the buffer. + mBuffer.write(bytes); + + if (mVerified == false && mBuffer.size() >= 4) + { + QByteArray foo = mBuffer.read(4); + if (QString(foo) == "foo"){ + kdDebug (14140) << k_funcinfo << "Bridge (" << name() << ") CONNECTION verified." << endl; + mVerified = true; + } + } + + while(mBuffer.size() > 0) + { + if (mBuffer.size() >= 4 && mLength == 0) + { + QByteArray array = mBuffer.read(4); + for (int i=0; i < 4; i++){ + ((char*)mLength)[i] = array[i]; + } + } + + if (mLength > 0 && mBuffer.size() >= mLength) + { + kdDebug (14140) << k_funcinfo << "Bridge (" << name() << ") read " << mLength << " bytes." << endl; + bytes = mBuffer.read(mLength); + mLength = 0; +// Message message = mFormatter->readMessage(bytes, true); +// emit messageReceived(message); + } + else + { + kdDebug (14140) << k_funcinfo << "Bridge (" << name() << ") waiting for " << mLength << " bytes." << endl; + break; + } + } +} + +//END + +//BEGIN Private Slot Methods + +void TcpTransportBridge::slotOnSocketConnectTimeout() +{ + kdDebug (14140) << k_funcinfo << "Bridge (" << name() << ") CONNECT timeout." << endl; + emit bridgeConnectTimeout(); + mSocket->deleteLater(); + mSocket = 0l; +} + +//END + + + + +TcpTransportBridge::Buffer::Buffer(Q_UINT32 length) +: QByteArray(length) +{ +} + +TcpTransportBridge::Buffer::~Buffer() +{ +} + +//BEGIN Public Methods + +void TcpTransportBridge::Buffer::write(const QByteArray& bytes) +{ + resize(size() + bytes.size()); + for (uint i=0; i < bytes.size(); i++){ + (*this)[size() + i] = bytes[i]; + } +} + +QByteArray TcpTransportBridge::Buffer::read(Q_UINT32 length) +{ + if (length >= size()) return QByteArray(); + + QByteArray buffer; + buffer.duplicate(data(), length); + + char *bytes = new char[size() - length]; + for(uint i=0; i < size() - length; i++){ + bytes[i] = data()[length + i]; + } + + duplicate(bytes, size() - length); + delete[] bytes; + + return buffer; +} + +//END + +} + +#include "transport.moc" + diff --git a/kopete/protocols/msn/transport.h b/kopete/protocols/msn/transport.h new file mode 100644 index 00000000..0dae0f32 --- /dev/null +++ b/kopete/protocols/msn/transport.h @@ -0,0 +1,167 @@ +/* + transport.h - Peer to peer transport + + Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com> + + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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; version 2 of the License. * + * * + ************************************************************************* +*/ + +#ifndef PEERTOPEERTRANSPORT_H +#define PEERTOPEERTRANSPORT_H + +//BEGIN QT Includes +#include <qobject.h> +#include <qguardedptr.h> +#include <qvaluelist.h> +//END + +//BEGIN KDE Includes +#include <ksocketaddress.h> +//END + +namespace KNetwork { +class KClientSocketBase; +} + +class MSNSwitchBoardSocket; + +namespace PeerToPeer { + +class MessageFormatter; +class TransportBridge; + +enum TransportBridgeType +{ + Tcp = 1, + Udp = 2 +}; + +/** + @author Gregg Edghill <gregg.edghill@gmail.com> */ +/** @brief Represents the protocol used to send and receive message between peers. */ +class Transport : public QObject +{ + Q_OBJECT +public: + /** @brief Creates a new instance of the class Transport. */ + Transport(QObject* parent, const char* name = 0l); + ~Transport(); + /** @brief Get a transport bridge with the specified address, port, type and identifier. */ + TransportBridge* getBridge(const QString& address, Q_UINT16 port, TransportBridgeType type, const QString& identifier); + /** @brief Sets the default transport bridge. */ + void setDefaultBridge(MSNSwitchBoardSocket* mss); + +private slots: + /** @brief Invokes when a message is received on a transport bridge. */ +// void slotOnReceive(Message& message); + /** @brief Invokes when a message is received on the default transport bridge (relay). */ + void slotOnReceive(const QString& contact, const QByteArray& bytes); + +private: + /** @brief Known SocketAddresses of peers. */ + QMap<QString, KNetwork::KInetSocketAddress> mAddresses; + /** @brief The list the connected transport bridges. */ + QValueList<TransportBridge*> mBridges; + /** @brief The default transport bridge (relay). */ + QGuardedPtr<MSNSwitchBoardSocket> mDefaultBridge; + /** @brief Message formatter used to ser/deser message. */ + MessageFormatter *mFormatter; +}; + +/** @brief Represents the channel connecting two peers. */ +class TransportBridge : public QObject +{ + Q_OBJECT +public: + virtual ~TransportBridge(); + +protected: + /** @brief Creates a new instance of the class TransportBridge with the specified address and formatter. */ + TransportBridge(const KNetwork::KInetSocketAddress& to, MessageFormatter* formatter, QObject* parent, const char* name = 0l); + /** @brief Creates a new instance of the class TransportBridge with the specified socket and formatter. */ + TransportBridge(KNetwork::KClientSocketBase* socket, MessageFormatter* formatter, QObject* parent, const char* name = 0l); + +public: + /** @brief Creates a connection between two peers. */ + void connect(); + /** @brief Disconnects the connection between two peers. */ + void disconnect(); + +protected slots: + virtual void slotOnConnect(); + virtual void slotOnDisconnect(); + virtual void slotOnError(int); + virtual void slotOnSocketClose(); + virtual void slotOnSocketConnect(); + virtual void slotOnSocketReceive(); + +signals: + void bridgeConnect(); + void bridgeDisconnect(); + void bridgeError(const QString& e); + void bytesReceived(const QByteArray&); + +protected: + + KNetwork::KInetSocketAddress mAddress; + bool mConnected; + MessageFormatter *mFormatter; + Q_UINT32 mLength; + KNetwork::KClientSocketBase *mSocket; + bool mVerified; +}; + +class TcpTransportBridge : public TransportBridge +{ + Q_OBJECT + friend class Transport; + +public: + virtual ~TcpTransportBridge(); + +private: + TcpTransportBridge(const KNetwork::KInetSocketAddress& to, MessageFormatter* formatter, QObject* parent, const char* name = 0l); + TcpTransportBridge(KNetwork::KClientSocketBase* socket, MessageFormatter* formatter, QObject* parent, const char* name = 0l); + +protected slots: + virtual void slotOnConnect(); + virtual void slotOnDisconnect(); + virtual void slotOnError(int); + virtual void slotOnSocketClose(); + virtual void slotOnSocketConnect(); + virtual void slotOnSocketReceive(); + +private slots: + void slotOnSocketConnectTimeout(); + +signals: + void bridgeConnectTimeout(); + +private: + class Buffer : public QByteArray + { + public: + Buffer(Q_UINT32 length = 0); + ~Buffer(); + + public: + void write(const QByteArray& bytes); + QByteArray read(Q_UINT32 length); + }; + + Buffer mBuffer; + Q_UINT32 mLength; +}; + + +} + +#endif diff --git a/kopete/protocols/msn/ui/Makefile.am b/kopete/protocols/msn/ui/Makefile.am new file mode 100644 index 00000000..08a7cbac --- /dev/null +++ b/kopete/protocols/msn/ui/Makefile.am @@ -0,0 +1,12 @@ +METASOURCES = AUTO +AM_CPPFLAGS = $(KOPETE_INCLUDES) \ + -I$(srcdir)/.. \ + $(all_includes) + +noinst_LTLIBRARIES = libkopetemsnui.la + +libkopetemsnui_la_SOURCES = msnadd.ui msndebugrawcommand_base.ui msninfo.ui \ + msneditaccountui.ui msneditaccountwidget.cpp + +EXTRA_DIST = msnadd.ui msninfo.ui + diff --git a/kopete/protocols/msn/ui/msnadd.ui b/kopete/protocols/msn/ui/msnadd.ui new file mode 100644 index 00000000..ff99bb92 --- /dev/null +++ b/kopete/protocols/msn/ui/msnadd.ui @@ -0,0 +1,97 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>msnAddUI</class> +<widget class="QWidget"> + <property name="name"> + <cstring>msnAddUI</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>397</width> + <height>347</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout21</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="text"> + <string>&MSN Passport ID:</string> + </property> + <property name="alignment"> + <set>AlignTop</set> + </property> + <property name="buddy" stdset="0"> + <cstring>addID</cstring> + </property> + <property name="toolTip" stdset="0"> + <string>The user ID of the MSN contact you would like to add.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The user ID of the MSN contact you would like to add. This should be in the form of a valid E-mail address.</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>addID</cstring> + </property> + <property name="toolTip" stdset="0"> + <string>The user ID of the MSN contact you would like to add.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The user ID of the MSN contact you would like to add. This should be in the form of a valid E-mail address.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string><i>(for example: joe@hotmail.com)</i></string> + </property> + <property name="alignment"> + <set>AlignVCenter|AlignRight</set> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer13</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>160</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kopete/protocols/msn/ui/msndebugrawcommand_base.ui b/kopete/protocols/msn/ui/msndebugrawcommand_base.ui new file mode 100644 index 00000000..3b98ce0d --- /dev/null +++ b/kopete/protocols/msn/ui/msndebugrawcommand_base.ui @@ -0,0 +1,107 @@ +<!DOCTYPE UI><UI version="3.1" stdsetdef="1"> +<class>MSNDebugRawCommand_base</class> +<widget class="QWidget"> + <property name="name"> + <cstring>MSNDebugRawCommand_base</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>320</width> + <height>201</height> + </rect> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel2</cstring> + </property> + <property name="text"> + <string>&Parameters:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_params</cstring> + </property> + </widget> + <widget class="QLineEdit" row="0" column="1"> + <property name="name"> + <cstring>m_command</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>TextLabel1</cstring> + </property> + <property name="text"> + <string>Co&mmand:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_command</cstring> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>m_params</cstring> + </property> + </widget> + <widget class="QCheckBox" row="2" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_addId</cstring> + </property> + <property name="text"> + <string>Add &ID</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="QCheckBox" row="3" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_addNewline</cstring> + </property> + <property name="text"> + <string>Add &new line</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + </widget> + <widget class="KTextEdit" row="5" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>m_msg</cstring> + </property> + <property name="textFormat"> + <enum>PlainText</enum> + </property> + </widget> + <widget class="QLabel" row="4" column="0" rowspan="1" colspan="2"> + <property name="name"> + <cstring>TextLabel3</cstring> + </property> + <property name="text"> + <string>Message:</string> + </property> + </widget> + </grid> +</widget> +<tabstops> + <tabstop>m_command</tabstop> + <tabstop>m_params</tabstop> + <tabstop>m_addId</tabstop> + <tabstop>m_addNewline</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>ktextedit.h</includehint> +</includehints> +</UI> diff --git a/kopete/protocols/msn/ui/msneditaccountui.ui b/kopete/protocols/msn/ui/msneditaccountui.ui new file mode 100644 index 00000000..5a3b8294 --- /dev/null +++ b/kopete/protocols/msn/ui/msneditaccountui.ui @@ -0,0 +1,1421 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>MSNEditAccountUI</class> +<author>Olivier Goffart</author> +<widget class="QWidget"> + <property name="name"> + <cstring>Form1</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>604</width> + <height>437</height> + </rect> + </property> + <property name="caption"> + <string>Account Preferences - MSN</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>0</number> + </property> + <widget class="QTabWidget"> + <property name="name"> + <cstring>tabWidget3</cstring> + </property> + <property name="tabShape"> + <enum>Rounded</enum> + </property> + <widget class="QWidget"> + <property name="name"> + <cstring>tab_connection</cstring> + </property> + <attribute name="title"> + <string>&Basic Setup</string> + </attribute> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer row="2" column="0"> + <property name="name"> + <cstring>spacer41</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>146</height> + </size> + </property> + </spacer> + <widget class="QGroupBox" row="1" column="0"> + <property name="name"> + <cstring>groupBox5</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="title"> + <string>Registration</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel6</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>1</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="text"> + <string>To connect to the Microsoft network, you will need a Microsoft Passport.<br><br>If you do not currently have a Passport, please click the button to create one.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>buttonRegister</cstring> + </property> + <property name="text"> + <string>Re&gister New Account</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox" row="0" column="0"> + <property name="name"> + <cstring>m_accountInfo</cstring> + </property> + <property name="title"> + <string>Account Information</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget" row="0" column="0"> + <property name="name"> + <cstring>layout14</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel1_3</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&MSN Passport ID:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_login</cstring> + </property> + <property name="toolTip" stdset="0"> + <string>The user ID of the MSN contact you would like to use.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The user ID of the MSN contact you would like to use. This should be in the form of a valid E-mail address.</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_login</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="toolTip" stdset="0"> + <string>The user ID of the MSN contact you would like to use.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The user ID of the MSN contact you would like to use. This should be in the form of a valid E-mail address.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="Kopete::UI::PasswordWidget" row="1" column="0"> + <property name="name"> + <cstring>m_password</cstring> + </property> + </widget> + <widget class="QCheckBox" row="2" column="0"> + <property name="name"> + <cstring>m_autologin</cstring> + </property> + <property name="text"> + <string>E&xclude from connect all</string> + </property> + <property name="checked"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>If you check this checkbox, the account will not be connected when you press the "Connect All" button, or at startup when automatic connection at startup is enabled.</string> + </property> + </widget> + <widget class="QCheckBox" row="3" column="0"> + <property name="name"> + <cstring>m_globalIdentity</cstring> + </property> + <property name="text"> + <string>Exclu&de from Global Identity</string> + </property> + </widget> + </grid> + </widget> + </grid> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>MSN &Settings</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_3</cstring> + </property> + <property name="font"> + <font> + <italic>1</italic> + </font> + </property> + <property name="text"> + <string><qt><b>Note:</b> These settings are applicable to all MSN accounts</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignCenter</set> + </property> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>global_settings_page</cstring> + </property> + <property name="title"> + <string>Global MSN Options</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>NotifyNewChat</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>1</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Au&tomatically open a chat window when someone starts a conversation</string> + </property> + <property name="whatsThis" stdset="0"> + <string>This option will notify you when a contact starts typing their message, before the message is sent or finished.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout13_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_4</cstring> + </property> + <property name="text"> + <string>Download the msn picture:</string> + </property> + <property name="whatsThis" stdset="0"> + <string><qt><p>Indicate when Kopete will download the display pictures of contacts</p> +<dl><dt>Only manually</dt><dd>The picture is not downloaded automatically. It is only downloaded when the user requests it</dd> +<dt>When a chat is open</dt><dd>The picture is downloaded when a conversation socket is opened, i.e. when you open a chat window</dd> +<dt>Automatically</dt><dd>Always try to download the picture if the contact has one. <b>Note:</b> this will open a socket, and let the user know you are downloading their picture.</dd></dl></string> + </property> + </widget> + <widget class="QComboBox"> + <item> + <property name="text"> + <string>Only Manually</string> + </property> + </item> + <item> + <property name="text"> + <string>When a Chat is Open</string> + </property> + </item> + <item> + <property name="text"> + <string>Automatically</string> + </property> + </item> + <property name="name"> + <cstring>DownloadPicture</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="currentItem"> + <number>2</number> + </property> + <property name="whatsThis" stdset="0"> + <string><qt><p>Indicate when Kopete will download the pictures of contacts</p> +<dl><dt>Only manually</dt><dd>The picture is not downloaded automatically. It is only downloaded when the user requests it</dd> +<dt>When a chat is open</dt><dd>The picture is downloaded when a conversation socket is opened, i.e. when you open a chat window</dd> +<dt>Automatically</dt><dd>Always try to download the picture if the contact has one. <b>Note:</b> this will open a socket, and let the user know you are downloading their picture.</dd></dl></string> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>useCustomEmoticons</cstring> + </property> + <property name="text"> + <string>&Download and show custom emoticons</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>MSN Messenger allows users to download and use custom emoticons. If this option is enabled, Kopete will download these emoticons and show them.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>exportEmoticons</cstring> + </property> + <property name="text"> + <string>E&xport the current emoticon theme to users</string> + </property> + <property name="toolTip" stdset="0"> + <string>Only work with emoticons in the PNG format</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Export all the emoticon themes as custom emoticons. +Only works for emoticons in the PNG format.</string> + </property> + </widget> + </vbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>privacy_page</cstring> + </property> + <property name="title"> + <string>Privacy</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>SendClientInfo</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Send client information</string> + </property> + <property name="accel"> + <string></string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="toolTip" stdset="0"> + <string><qt>Make it possible for your contacts to detect if you are using Kopete.<br>We recommend leaving this checked.</qt></string> + </property> + <property name="whatsThis" stdset="0"> + <string>Third party MSN clients, such as Kopete, give users the ability to let other third party clients guess which client they are using. We recommend leaving this checkbox checked.</string> + </property> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>SendTypingNotification</cstring> + </property> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Send &typing notifications</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string><qt>Check this box to send <b>Typing notifications</b> to your contacts. When you are composing a message, you might want your contact to know that you are typing so that he knows you are answering.</qt></string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout28</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>SendJabber</cstring> + </property> + <property name="text"> + <string>Expose my Jabber account to Jabber users</string> + </property> + <property name="checked"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>If you have a Jabber account, you may let Jabber users on an MSN gateway know that you are also using Jabber.</string> + </property> + </widget> + <widget class="KComboBox"> + <property name="name"> + <cstring>JabberAccount</cstring> + </property> + <property name="editable"> + <bool>true</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>If you have a Jabber account, you may let Jabber users on an MSN gateway know that you are also using Jabber.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer26</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>61</width> + <height>21</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer25</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_5</cstring> + </property> + <property name="text"> + <string>There are also privacy options in the "Contacts" tab</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignCenter</set> + </property> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer20</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>31</width> + <height>40</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab_info</cstring> + </property> + <attribute name="title"> + <string>User &Info</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout22_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel2_2_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>&Nickname:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_displayName</cstring> + </property> + <property name="whatsThis" stdset="0"> + <string>The alias you would like to use on MSN. You may change this at any time you wish.</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_displayName</cstring> + </property> + <property name="readOnly"> + <bool>false</bool> + </property> + <property name="whatsThis" stdset="0"> + <string>The alias you would like to use on MSN. You may change this at any time you wish.</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>m_phones</cstring> + </property> + <property name="title"> + <string>Phone Numbers</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel5</cstring> + </property> + <property name="text"> + <string>Hom&e:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_phh</cstring> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>TextLabel6</cstring> + </property> + <property name="text"> + <string>&Work:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_phw</cstring> + </property> + </widget> + <widget class="QLineEdit" row="0" column="1"> + <property name="name"> + <cstring>m_phw</cstring> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>m_phh</cstring> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>TextLabel7</cstring> + </property> + <property name="text"> + <string>&Mobile:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_phm</cstring> + </property> + </widget> + <widget class="QLineEdit" row="2" column="1"> + <property name="name"> + <cstring>m_phm</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox2</cstring> + </property> + <property name="title"> + <string>Display Picture</string> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout17</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_useDisplayPicture</cstring> + </property> + <property name="text"> + <string>E&xport a display picture</string> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1_2</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>5</hsizetype> + <vsizetype>5</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Please select a square image. The image will be scaled to 96x96.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout13</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>m_selectImage</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>&Select Image...</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer5</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>61</width> + <height>21</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7_2_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout16</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="spacing"> + <number>0</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>m_displayPicture</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>0</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>96</width> + <height>96</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>96</width> + <height>96</height> + </size> + </property> + <property name="frameShape"> + <enum>GroupBoxPanel</enum> + </property> + <property name="scaledContents"> + <bool>true</bool> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7_2</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Minimum</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>1</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </hbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer16</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>21</height> + </size> + </property> + </spacer> + <widget class="QLabel"> + <property name="name"> + <cstring>m_warning_1</cstring> + </property> + <property name="paletteForegroundColor"> + <color> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>WARNING: You need to be connected to modify this page.</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>tab_contacts</cstring> + </property> + <attribute name="title"> + <string>Con&tacts</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>label_font</cstring> + </property> + <property name="text"> + <string><i>Italics</i> contacts are not on your contact list.<br> +<br> +<b>Bold</b> contacts are in your contact list but you are not in their contact list.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout6</cstring> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="0" column="2"> + <property name="name"> + <cstring>textLabel2</cstring> + </property> + <property name="text"> + <string>Bloc&ked contacts:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_BL</cstring> + </property> + </widget> + <widget class="QListBox" row="1" column="0"> + <property name="name"> + <cstring>m_AL</cstring> + </property> + </widget> + <widget class="QLayoutWidget" row="1" column="1"> + <property name="name"> + <cstring>layout4</cstring> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QPushButton"> + <property name="name"> + <cstring>m_blockButton</cstring> + </property> + <property name="text"> + <string>&></string> + </property> + </widget> + <widget class="QPushButton"> + <property name="name"> + <cstring>m_allowButton</cstring> + </property> + <property name="text"> + <string>&<</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer13</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>20</height> + </size> + </property> + </spacer> + </vbox> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Allo&wed contacts:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_AL</cstring> + </property> + </widget> + <widget class="QListBox" row="1" column="2"> + <property name="name"> + <cstring>m_BL</cstring> + </property> + </widget> + </grid> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout58</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer47</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>41</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_blp</cstring> + </property> + <property name="text"> + <string>Block all users not in 'Allowed' &list</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Checking this box will block all users not explicitly shown in the allowed list here, including any contacts not on your contact list.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer50</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>41</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout59</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <spacer> + <property name="name"> + <cstring>spacer48</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>81</width> + <height>20</height> + </size> + </property> + </spacer> + <widget class="QPushButton"> + <property name="name"> + <cstring>m_RLButton</cstring> + </property> + <property name="maximumSize"> + <size> + <width>200</width> + <height>32767</height> + </size> + </property> + <property name="text"> + <string>View &Reverse List</string> + </property> + <property name="toolTip" stdset="0"> + <string>The reverse list is the list of contacts who added you to their own contact list.</string> + </property> + <property name="whatsThis" stdset="0"> + <string>The reverse list is the list of contacts who added you to their own contact list.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>spacer49</cstring> + </property> + <property name="orientation"> + <enum>Horizontal</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>111</width> + <height>20</height> + </size> + </property> + </spacer> + </hbox> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>m_warning_2</cstring> + </property> + <property name="paletteForegroundColor"> + <color> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </property> + <property name="font"> + <font> + <bold>1</bold> + </font> + </property> + <property name="text"> + <string>WARNING: You need to be connected to modify this page</string> + </property> + <property name="alignment"> + <set>WordBreak|AlignVCenter</set> + </property> + </widget> + </vbox> + </widget> + <widget class="QWidget"> + <property name="name"> + <cstring>TabPage</cstring> + </property> + <attribute name="title"> + <string>Co&nnection</string> + </attribute> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QGroupBox"> + <property name="name"> + <cstring>groupBox66</cstring> + </property> + <property name="title"> + <string>Connection Preferences (for advanced users)</string> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>optionOverrideServer</cstring> + </property> + <property name="text"> + <string>&Override default server information</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout20</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout19</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="spacing"> + <number>3</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>labelServer</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Ser&ver /</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_serverName</cstring> + </property> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>labelPort</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>po&rt:</string> + </property> + <property name="buddy" stdset="0"> + <cstring>m_serverPort</cstring> + </property> + </widget> + </hbox> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_serverName</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>3</hsizetype> + <vsizetype>0</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>messenger.hotmail.com</string> + </property> + <property name="toolTip" stdset="0"> + <string>Only modify these values if you want to use a special IM proxy server, like SIMP</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Only modify these values if you want to use a special IM proxy server, like SIMP</string> + </property> + </widget> + <widget class="QSpinBox"> + <property name="name"> + <cstring>m_serverPort</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maxValue"> + <number>65535</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + <property name="value"> + <number>1863</number> + </property> + <property name="toolTip" stdset="0"> + <string>Only modify these values if you want to use a special IM proxy server, like SIMP</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Only modify these values if you want to use a special IM proxy server, like SIMP</string> + </property> + </widget> + </hbox> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>optionUseHttpMethod</cstring> + </property> + <property name="text"> + <string>Use &HTTP method</string> + </property> + <property name="whatsThis" stdset="0"> + <string>Connect to MSN Messenger using an HTTP-like protocol on port 80. +This may be used to connect on a network with a restrictive firewall. +Only check this option if the normal connection doesn't work.</string> + </property> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout22</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_useWebcamPort</cstring> + </property> + <property name="text"> + <string>S&pecify a base port for incoming webcam connections:</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If you are behind a firewall, you may specify a base port to use for the incoming connection, and configure your firewall to accept connections on a range of 10 ports, starting at this one. Incoming connections are used for the webcam. If you don't specify a port yourself, the operating system will choose an available port for you. It is recommended to leave the checkbox unchecked.</string> + </property> + </widget> + <widget class="QSpinBox"> + <property name="name"> + <cstring>m_webcamPort</cstring> + </property> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="maxValue"> + <number>65535</number> + </property> + <property name="minValue"> + <number>1</number> + </property> + <property name="value"> + <number>6891</number> + </property> + <property name="whatsThis" stdset="0"> + <string>If you are behind a firewall, you may specify a base port to use for the incoming connection, and configure your firewall to accept connections on a range of 10 ports, starting at this one. Incoming connections are used for the webcam. If you don't specify a port yourself, the operating system will choose an available port for you. It is recommended to leave the checkbox unchecked.</string> + </property> + </widget> + </hbox> + </widget> + </vbox> + </widget> + <spacer> + <property name="name"> + <cstring>spacer7</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>21</width> + <height>70</height> + </size> + </property> + </spacer> + </vbox> + </widget> + </widget> + <widget class="QLabel"> + <property name="name"> + <cstring>labelStatusMessage</cstring> + </property> + <property name="text"> + <string></string> + </property> + <property name="alignment"> + <set>AlignCenter</set> + </property> + </widget> + </vbox> +</widget> +<customwidgets> + <customwidget> + <class>Kopete::UI::PasswordWidget</class> + <header location="local">kopetepasswordwidget.h</header> + <sizehint> + <width>50</width> + <height>50</height> + </sizehint> + <container>0</container> + <sizepolicy> + <hordata>1</hordata> + <verdata>0</verdata> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + <pixmap>image0</pixmap> + <signal>changed()</signal> + </customwidget> +</customwidgets> +<images> + <image name="image0"> + <data format="PNG" length="868">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000032b49444154388db59531681b6714c77f32373c8186ef0305eea0050932f8201d944d493bc4d0a1a21e4427bb533a74299dbc25905288a7d0b9836932d58116eac1411932388ba421a5a7a17005174e83e00e2cb80f6ab83708d2e18bec8ada26d0f4c1c1ddbbf7fdeeff3efeefbbda70346419b76fdd7ecd3b88e16858ab2dc183c3c1ebee7a97a99b521515d969f65610e71cd971c6f8d7312ccef3c152e9b39f9e11351d36164acdb819d4a9b4c4362ce5a2c48a45162588253ff5cfe5a2c406862405d9138e5eea2a18609a4fb12d212d7ea42c334089ac92e6423113cab902826d4227568a002480a942780dead16a2767e0ca55949a81668023b2c2e8952139748c270e58aa115aebc2675b86b80b6143710aa1b9049ccd336e064a5979e8e039ec7f5f78544368af1b24807ca64cff50befba6a0b765d8be2b67f00bc1562c95e6441646afe40d54b9f36948af2fb4df078722440c0e2af6f70a064f0be2568beea6c5885b01af2d6f4a2db10dc8ff128e0edc19f4f32f8576dbe1707022fcf2b4647babce175f8780f0c31307a7e0162bdc55c5e52247e742fabbc31843af2f9886c32d40d4b0fb4849278ef20476ee59c62f7ced3831848d55f0aa62816ca6de11ad37ed2fa10f1ce9c4619ac2c647824a45dc1100f2a9e2542e067b9f82155f108adf539c61f781924efc0745c0be57273240b08409e62ac508d0f085c94c112c83e778a54608434331733cbc9f331a5bf2636f85a855bfda15f9694e27565ad785e99fcae0a062fb6e4479a2f43e16eacd3a0fef433175ec7e95a1aa98a6d0e95454f355f2bff65803e8f5bddbf7f70a0687393bf72ced2e74ba253bdfb631a1c139872e948d7e487c83ab15979a2301dcba033a373c7e52f0f851c1f885d0ed080ec88f7374ae672b7f3b72249b115143389fce7f4e5e91d11398cefd986e6c099816839fbd1bd2c9b91ad3147afd16a32387534580ac58957c0e3ece485230d77c5ba6a1f4fa42ef9398719253153e1f5f8f687f9013df80f16684c1e0161969b20aae0d47437fc007d0f950882210c19fad81bf24f04e399701a04820380769a2e485e28a0b14b380e4a5927059e85be67cac5dfae63fc61af87fd4ff027ed7f0e16858fb1ba5cd86c64770b2e90000000049454e44ae426082</data> + </image> +</images> +<connections> + <connection> + <sender>m_useDisplayPicture</sender> + <signal>toggled(bool)</signal> + <receiver>m_selectImage</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>m_useDisplayPicture</sender> + <signal>toggled(bool)</signal> + <receiver>m_selectImage</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>m_useDisplayPicture</sender> + <signal>toggled(bool)</signal> + <receiver>textLabel1_2</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>optionOverrideServer</sender> + <signal>toggled(bool)</signal> + <receiver>labelServer</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>optionOverrideServer</sender> + <signal>toggled(bool)</signal> + <receiver>m_serverName</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>optionOverrideServer</sender> + <signal>toggled(bool)</signal> + <receiver>labelPort</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>optionOverrideServer</sender> + <signal>toggled(bool)</signal> + <receiver>m_serverPort</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>m_useDisplayPicture</sender> + <signal>toggled(bool)</signal> + <receiver>m_displayPicture</receiver> + <slot>setEnabled(bool)</slot> + </connection> + <connection> + <sender>m_useWebcamPort</sender> + <signal>toggled(bool)</signal> + <receiver>m_webcamPort</receiver> + <slot>setEnabled(bool)</slot> + </connection> +</connections> +<tabstops> + <tabstop>tabWidget3</tabstop> + <tabstop>optionOverrideServer</tabstop> + <tabstop>m_serverName</tabstop> + <tabstop>m_serverPort</tabstop> + <tabstop>optionUseHttpMethod</tabstop> + <tabstop>m_login</tabstop> + <tabstop>m_autologin</tabstop> + <tabstop>buttonRegister</tabstop> + <tabstop>m_displayName</tabstop> + <tabstop>m_phw</tabstop> + <tabstop>m_phh</tabstop> + <tabstop>m_phm</tabstop> + <tabstop>m_useDisplayPicture</tabstop> + <tabstop>m_selectImage</tabstop> + <tabstop>m_AL</tabstop> + <tabstop>m_blockButton</tabstop> + <tabstop>m_allowButton</tabstop> + <tabstop>m_BL</tabstop> + <tabstop>m_blp</tabstop> + <tabstop>m_RLButton</tabstop> +</tabstops> +<layoutdefaults spacing="6" margin="11"/> +<includehints> + <includehint>kopetepasswordwidget.h</includehint> + <includehint>kcombobox.h</includehint> + <includehint>klineedit.h</includehint> +</includehints> +</UI> diff --git a/kopete/protocols/msn/ui/msneditaccountwidget.cpp b/kopete/protocols/msn/ui/msneditaccountwidget.cpp new file mode 100644 index 00000000..1829f41d --- /dev/null +++ b/kopete/protocols/msn/ui/msneditaccountwidget.cpp @@ -0,0 +1,369 @@ +/* + msneditaccountwidget.cpp - MSN Account Widget + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msneditaccountwidget.h" + +#include <qcheckbox.h> +#include <qgroupbox.h> +#include <qimage.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qlineedit.h> +#include <qlistbox.h> +#include <qpushbutton.h> +#include <qregexp.h> +#include <qspinbox.h> +#include <kcombobox.h> + +#include <kautoconfig.h> +#include <kfiledialog.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kstandarddirs.h> +#include <kio/netaccess.h> +#include <kdebug.h> +#include <kpassdlg.h> +#include <krun.h> +#include <kconfig.h> +#include <kpixmapregionselectordialog.h> + +#include "kopeteuiglobal.h" +#include "kopeteglobal.h" + +#include "kopetepasswordwidget.h" +#include "kopeteaccountmanager.h" + +#include "msnaccount.h" +#include "msncontact.h" +#include "msneditaccountui.h" +#include "msnnotifysocket.h" +#include "msnprotocol.h" + +class MSNEditAccountWidgetPrivate +{ +public: + MSNProtocol *protocol; + KAutoConfig *autoConfig; + MSNEditAccountUI *ui; + + QString pictureUrl; + QImage pictureData; +}; + +MSNEditAccountWidget::MSNEditAccountWidget( MSNProtocol *proto, Kopete::Account *account, QWidget *parent, const char * /* name */ ) +: QWidget( parent ), KopeteEditAccountWidget( account ) +{ + d = new MSNEditAccountWidgetPrivate; + + d->protocol=proto; + + ( new QVBoxLayout( this, 0, 0 ) )->setAutoAdd( true ); + + d->ui = new MSNEditAccountUI( this ); + + d->autoConfig = new KAutoConfig( d->ui ); + d->autoConfig->addWidget( d->ui->global_settings_page, "MSN" ); + d->autoConfig->addWidget( d->ui->privacy_page, "MSN" ); + //the JabberAccount need to be saved as text, and can't be handled by kautoconfig + d->autoConfig->ignoreSubWidget( d->ui->JabberAccount ); + d->autoConfig->retrieveSettings( true ); + + //Get a list of all jabber accounts + KGlobal::config()->setGroup("MSN"); + QString jab_account=KGlobal::config()->readEntry("JabberAccount"); + + QPtrList<Kopete::Account> accounts = Kopete::AccountManager::self()->accounts(); + for(Kopete::Account *a=accounts.first() ; a; a=accounts.next() ) + { + if(a->protocol()->pluginId()=="JabberProtocol") + { + d->ui->JabberAccount->insertItem(a->accountId()); + if( jab_account.isEmpty() ) + jab_account=a->accountId(); + } + } + d->ui->JabberAccount->setCurrentText(jab_account); + + // FIXME: actually, I don't know how to set fonts for qlistboxitem - Olivier + d->ui->label_font->hide(); + + // default fields + if ( account ) + { + KConfigGroup * config=account->configGroup(); + + d->ui->m_login->setText( account->accountId() ); + d->ui->m_password->load( &static_cast<MSNAccount *>(account)->password() ); + + //remove me after we can change account ids (Matt) + d->ui->m_login->setDisabled( true ); + d->ui->m_autologin->setChecked( account->excludeConnect() ); + if ( ( static_cast<MSNAccount*>(account)->serverName() != "messenger.hotmail.com" ) || ( static_cast<MSNAccount*>(account)->serverPort() != 1863) ) { + d->ui->optionOverrideServer->setChecked( true ); + } + + d->ui->m_webcamPort->setDisabled(true); + uint port=config->readNumEntry("WebcamPort" ,0); + d->ui->m_useWebcamPort->setChecked( port != 0); + d->ui->m_webcamPort->setValue( port != 0 ? port : 6891 ); + + d->ui->optionUseHttpMethod->setChecked( static_cast<MSNAccount*>(account)->useHttpMethod() ); + + MSNContact *myself = static_cast<MSNContact *>( account->myself() ); + + d->ui->m_displayName->setText( myself->property( Kopete::Global::Properties::self()->nickName()).value().toString() ); + d->ui->m_phw->setText( config->readEntry("PHW") ); + d->ui->m_phm->setText( config->readEntry("PHM") ); + d->ui->m_phh->setText( config->readEntry("PHH") ); + + bool connected = account->isConnected(); + if ( connected ) + { + d->ui->m_warning_1->hide(); + d->ui->m_warning_2->hide(); + } + d->ui->m_phones->setEnabled( connected ); + d->ui->m_displayName->setEnabled( connected ); + d->ui->m_allowButton->setEnabled( connected ); + d->ui->m_blockButton->setEnabled( connected ); + + MSNAccount *m_account = static_cast<MSNAccount*>( account ); + d->ui->m_serverName->setText( m_account->serverName() ); + d->ui->m_serverPort->setValue( m_account->serverPort() ); + + QStringList blockList = config->readListEntry( "blockList" ); + QStringList allowList = config->readListEntry( "allowList" ); + //QStringList reverseList = config->readListEntry("reverseList" ); + + for ( QStringList::Iterator it = blockList.begin(); it != blockList.end(); ++it ) + d->ui->m_BL->insertItem( *it ); + + for ( QStringList::Iterator it = allowList.begin(); it != allowList.end(); ++it ) + d->ui->m_AL->insertItem( *it ); + + d->ui->m_blp->setChecked( config->readEntry( "BLP" ) == "BL" ); + + d->pictureUrl = locateLocal( "appdata", "msnpicture-" + + account->accountId().lower().replace( QRegExp("[./~]" ), "-" ) + ".png" ); + d->ui->m_displayPicture->setPixmap( d->pictureUrl ); + + d->ui->m_useDisplayPicture->setChecked( config->readBoolEntry( "exportCustomPicture" )); + + // Global Identity + d->ui->m_globalIdentity->setChecked( config->readBoolEntry("ExcludeGlobalIdentity", false) ); + } + else + { + d->ui->tab_contacts->setDisabled( true ); + d->ui->m_displayName->setDisabled( true ); + d->ui->m_phones->setDisabled( true ); + } + + connect( d->ui->m_allowButton, SIGNAL( clicked() ), this, SLOT( slotAllow() ) ); + connect( d->ui->m_blockButton, SIGNAL( clicked() ), this, SLOT( slotBlock() ) ); + connect( d->ui->m_selectImage, SIGNAL( clicked() ), this, SLOT( slotSelectImage() ) ); + connect( d->ui->m_RLButton, SIGNAL( clicked() ), this, SLOT( slotShowReverseList() ) ); + connect( d->ui->buttonRegister, SIGNAL(clicked()), this, SLOT(slotOpenRegister())); + QWidget::setTabOrder( d->ui->m_login, d->ui->m_password->mRemembered ); + QWidget::setTabOrder( d->ui->m_password->mRemembered, d->ui->m_password->mPassword ); + QWidget::setTabOrder( d->ui->m_password->mPassword, d->ui->m_autologin ); +} + +MSNEditAccountWidget::~MSNEditAccountWidget() +{ + delete d; +} + +Kopete::Account * MSNEditAccountWidget::apply() +{ + d->autoConfig->saveSettings(); + KGlobal::config()->setGroup("MSN"); + KGlobal::config()->writeEntry("JabberAccount", d->ui->JabberAccount->currentText()); + + if ( !account() ) + setAccount( new MSNAccount( d->protocol, d->ui->m_login->text() ) ); + + KConfigGroup *config=account()->configGroup(); + + account()->setExcludeConnect( d->ui->m_autologin->isChecked() ); + d->ui->m_password->save( &static_cast<MSNAccount *>(account())->password() ); + + config->writeEntry( "exportCustomPicture", d->ui->m_useDisplayPicture->isChecked() ); + if (d->ui->optionOverrideServer->isChecked() ) { + config->writeEntry( "serverName", d->ui->m_serverName->text() ); + config->writeEntry( "serverPort", d->ui->m_serverPort->value() ); + } + else { + config->writeEntry( "serverName", "messenger.hotmail.com" ); + config->writeEntry( "serverPort", "1863" ); + } + + config->writeEntry( "useHttpMethod", d->ui->optionUseHttpMethod->isChecked() ); + + if(d->ui->m_useWebcamPort->isChecked()) + config->writeEntry( "WebcamPort" , d->ui->m_webcamPort->value() ); + else + config->writeEntry( "WebcamPort" , 0 ); + + // Global Identity + config->writeEntry( "ExcludeGlobalIdentity", d->ui->m_globalIdentity->isChecked() ); + + // Save the avatar image + if( d->ui->m_useDisplayPicture->isChecked() && !d->pictureData.isNull() ) + { + d->pictureUrl = locateLocal( "appdata", "msnpicture-" + + account()->accountId().lower().replace( QRegExp("[./~]" ), "-" ) + ".png" ); + if ( d->pictureData.save( d->pictureUrl, "PNG" ) ) + { + static_cast<MSNAccount *>( account() )->setPictureUrl( d->pictureUrl ); + } + else + { + KMessageBox::sorry( this, i18n( "<qt>An error occurred when trying to change the display picture.<br>" + "Make sure that you have selected a correct image file</qt>" ), i18n( "MSN Plugin" ) ); + } + } + + static_cast<MSNAccount *>( account() )->resetPictureObject(); + + if ( account()->isConnected() ) + { + MSNContact *myself = static_cast<MSNContact *>( account()->myself() ); + MSNNotifySocket *notify = static_cast<MSNAccount *>( account() )->notifySocket(); + if ( d->ui->m_displayName->text() != myself->property( Kopete::Global::Properties::self()->nickName()).value().toString() ) + static_cast<MSNAccount *>( account() )->setPublicName( d->ui->m_displayName->text() ); + + if ( notify ) + { + if ( d->ui->m_phw->text() != myself->phoneWork() && ( !d->ui->m_phw->text().isEmpty() || !myself->phoneWork().isEmpty() ) ) + notify->changePhoneNumber( "PHW", d->ui->m_phw->text() ); + if( d->ui->m_phh->text() != myself->phoneHome() && ( !d->ui->m_phh->text().isEmpty() || !myself->phoneHome().isEmpty() ) ) + notify->changePhoneNumber( "PHH", d->ui->m_phh->text() ); + if( d->ui->m_phm->text() != myself->phoneMobile() && ( !d->ui->m_phm->text().isEmpty() || !myself->phoneMobile().isEmpty() ) ) + notify->changePhoneNumber( "PHM", d->ui->m_phm->text() ); + // (the && .isEmpty is because one can be null and the other empty) + + if ( ( config->readEntry("BLP") == "BL" ) != d->ui->m_blp->isChecked() ) + { + // Yes, I know, calling sendCommand here is not very clean - Olivier + notify->sendCommand( "BLP", d->ui->m_blp->isChecked() ? "BL" : "AL" ); + } + } + } + return account(); +} + +bool MSNEditAccountWidget::validateData() +{ + QString userid = d->ui->m_login->text(); + if ( MSNProtocol::validContactId( userid ) ) + return true; + + KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget(), KMessageBox::Sorry, + i18n( "<qt>You must enter a valid email address.</qt>" ), i18n( "MSN Plugin" ) ); + return false; +} + +void MSNEditAccountWidget::slotAllow() +{ + //TODO: play with multiple selection + QListBoxItem *item = d->ui->m_BL->selectedItem(); + if ( !item ) + return; + + QString handle = item->text(); + + MSNNotifySocket *notify = static_cast<MSNAccount *>( account() )->notifySocket(); + if ( !notify ) + return; + notify->removeContact( handle, MSNProtocol::BL, QString::null, QString::null ); + + d->ui->m_BL->takeItem( item ); + d->ui->m_AL->insertItem( item ); +} + +void MSNEditAccountWidget::slotBlock() +{ + //TODO: play with multiple selection + QListBoxItem *item = d->ui->m_AL->selectedItem(); + if ( !item ) + return; + + QString handle = item->text(); + + MSNNotifySocket *notify = static_cast<MSNAccount *>( account() )->notifySocket(); + if ( !notify ) + return; + + notify->removeContact( handle, MSNProtocol::AL, QString::null, QString::null ); + + d->ui->m_AL->takeItem( item ); + d->ui->m_BL->insertItem( item ); +} + +void MSNEditAccountWidget::slotShowReverseList() +{ + QStringList reverseList = account()->configGroup()->readListEntry( "reverseList" ); + KMessageBox::informationList( this, i18n( "Here you can see a list of contacts who added you to their contact list" ), reverseList, + i18n( "Reverse List - MSN Plugin" ) ); +} + +void MSNEditAccountWidget::slotSelectImage() +{ + QString path = 0; + bool remoteFile = false; + KURL filePath = KFileDialog::getImageOpenURL( QString::null, this, i18n( "MSN Display Picture" ) ); + if( filePath.isEmpty() ) + return; + + if( !filePath.isLocalFile() ) { + if(!KIO::NetAccess::download( filePath, path, this )) { + KMessageBox::sorry( this, i18n( "Downloading of display image failed" ), i18n( "MSN Plugin" ) ); + return; + } + remoteFile = true; + } + else path = filePath.path(); + + QImage img( path ); + img = KPixmapRegionSelectorDialog::getSelectedImage( QPixmap(img), 96, 96, this ); + + if(!img.isNull()) + { + img = MSNProtocol::protocol()->scalePicture(img); + + d->ui->m_displayPicture->setPixmap( QPixmap(img) ); + d->pictureData = img; + } + else + { + KMessageBox::sorry( this, i18n( "<qt>An error occurred when trying to change the display picture.<br>" + "Make sure that you have selected a correct image file</qt>" ), i18n( "MSN Plugin" ) ); + } + if( remoteFile ) KIO::NetAccess::removeTempFile( path ); +} + +void MSNEditAccountWidget::slotOpenRegister() +{ + KRun::runURL( "http://register.passport.net/", "text/html" ); +} + +#include "msneditaccountwidget.moc" + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/ui/msneditaccountwidget.h b/kopete/protocols/msn/ui/msneditaccountwidget.h new file mode 100644 index 00000000..2b8b8f6e --- /dev/null +++ b/kopete/protocols/msn/ui/msneditaccountwidget.h @@ -0,0 +1,59 @@ +/* + msneditaccountwidget.h - MSN Account Widget + + Copyright (c) 2003 by Olivier Goffart <ogoffart @ kde.org> + Copyright (c) 2003 by Martijn Klingens <klingens@kde.org> + + Kopete (c) 2002-2003 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MSNEDITACCOUNTWIDEGET_H +#define MSNEDITACCOUNTWIDEGET_H + +#include <qwidget.h> + +#include "editaccountwidget.h" + +namespace Kopete { class Account; } + +class MSNProtocol; + +class MSNEditAccountWidgetPrivate; + +/** + * @author Olivier Goffart <ogoffart @ kde.org> + */ +class MSNEditAccountWidget : public QWidget, public KopeteEditAccountWidget +{ + Q_OBJECT + +public: + MSNEditAccountWidget( MSNProtocol *proto, Kopete::Account *account, QWidget *parent = 0, const char *name = 0 ); + ~MSNEditAccountWidget(); + virtual bool validateData(); + virtual Kopete::Account * apply(); + +private slots: + void slotAllow(); + void slotBlock(); + void slotShowReverseList(); + void slotSelectImage(); + void slotOpenRegister(); + +private: + MSNEditAccountWidgetPrivate *d; +}; + +#endif + +// vim: set noet ts=4 sts=4 sw=4: + diff --git a/kopete/protocols/msn/ui/msninfo.ui b/kopete/protocols/msn/ui/msninfo.ui new file mode 100644 index 00000000..17f1eecd --- /dev/null +++ b/kopete/protocols/msn/ui/msninfo.ui @@ -0,0 +1,221 @@ +<!DOCTYPE UI><UI version="3.3" stdsetdef="1"> +<class>MSNInfo</class> +<widget class="QWidget"> + <property name="name"> + <cstring>MSNInfo</cstring> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>457</width> + <height>360</height> + </rect> + </property> + <vbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout22</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel2_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Email address:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_id</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>Layout22_2</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <property name="margin"> + <number>0</number> + </property> + <property name="spacing"> + <number>6</number> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>TextLabel2_2_2</cstring> + </property> + <property name="sizePolicy"> + <sizepolicy> + <hsizetype>4</hsizetype> + <vsizetype>4</vsizetype> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Display name:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_displayName</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </hbox> + </widget> + <widget class="QLayoutWidget"> + <property name="name"> + <cstring>layout3</cstring> + </property> + <hbox> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel"> + <property name="name"> + <cstring>textLabel1</cstring> + </property> + <property name="text"> + <string>Personal message:</string> + </property> + </widget> + <widget class="QLineEdit"> + <property name="name"> + <cstring>m_personalMessage</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </hbox> + </widget> + <widget class="QGroupBox"> + <property name="name"> + <cstring>GroupBox2</cstring> + </property> + <property name="title"> + <string>Phones</string> + </property> + <grid> + <property name="name"> + <cstring>unnamed</cstring> + </property> + <widget class="QLabel" row="1" column="0"> + <property name="name"> + <cstring>TextLabel5</cstring> + </property> + <property name="text"> + <string>Home:</string> + </property> + </widget> + <widget class="QLabel" row="0" column="0"> + <property name="name"> + <cstring>TextLabel6</cstring> + </property> + <property name="text"> + <string>Work:</string> + </property> + </widget> + <widget class="QLineEdit" row="0" column="1"> + <property name="name"> + <cstring>m_phw</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLineEdit" row="1" column="1"> + <property name="name"> + <cstring>m_phh</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + <widget class="QLabel" row="2" column="0"> + <property name="name"> + <cstring>TextLabel7</cstring> + </property> + <property name="text"> + <string>Mobile:</string> + </property> + </widget> + <widget class="QLineEdit" row="2" column="1"> + <property name="name"> + <cstring>m_phm</cstring> + </property> + <property name="readOnly"> + <bool>true</bool> + </property> + </widget> + </grid> + </widget> + <widget class="QCheckBox"> + <property name="name"> + <cstring>m_reversed</cstring> + </property> + <property name="text"> + <string>I am on &the contact list of this contact</string> + </property> + <property name="toolTip" stdset="0"> + <string>Show whether you are on the contact list of this user</string> + </property> + <property name="whatsThis" stdset="0"> + <string>If this box is checked, you are on this user's contact list. +If not, the user has not added you to their list, or has removed you.</string> + </property> + </widget> + <spacer> + <property name="name"> + <cstring>Spacer10</cstring> + </property> + <property name="orientation"> + <enum>Vertical</enum> + </property> + <property name="sizeType"> + <enum>Expanding</enum> + </property> + <property name="sizeHint"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </vbox> +</widget> +<layoutdefaults spacing="6" margin="11"/> +</UI> diff --git a/kopete/protocols/msn/webcam.cpp b/kopete/protocols/msn/webcam.cpp new file mode 100644 index 00000000..db27d65f --- /dev/null +++ b/kopete/protocols/msn/webcam.cpp @@ -0,0 +1,891 @@ +/* + Copyright (c) 2005 by Olivier Goffart <ogoffart@ kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include "webcam.h" + +#if MSN_WEBCAM + +#include <stdlib.h> +#include <kdebug.h> +#include <qregexp.h> +#include <kbufferedsocket.h> +#include <klocale.h> +#include <kserversocket.h> +#include <kmessagebox.h> +#include <qlabel.h> +#include <qguardedptr.h> +#include <qtimer.h> +#include <qevent.h> +#include <qdatetime.h> +#include <kconfig.h> + +#include "dispatcher.h" + +#include "mimicwrapper.h" +#include "msnwebcamdialog.h" + + +#include "avdevice/videodevicepool.h" + +using namespace KNetwork; + +namespace P2P { + +Webcam::Webcam(Who who, const QString& to, Dispatcher *parent, Q_UINT32 sessionId) + : TransferContext(to,parent,sessionId) , m_who(who) , m_timerId(0) +{ + setType(P2P::WebcamType); + m_direction = Incoming; + m_listener = 0l; + m_webcamSocket=0L; +// m_webcamState=wsNegotiating; + + m_mimic=0L; + m_widget=0L; + + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + + // Read the configuration to get the number of frame per second to send + int webCamFps=config->readNumEntry("WebcamFPS", 25); + m_timerFps = 1000 / webCamFps; +} + +Webcam::~Webcam() +{ + kdDebug(14140) << k_funcinfo<< "################################################" << endl; + m_dispatcher=0l; + delete m_mimic; + delete m_webcamSocket; + delete m_widget; + + if(m_timerId != 0) //if we were sending + { + Kopete::AV::VideoDevicePool *videoDevice = Kopete::AV::VideoDevicePool::self(); + videoDevice->stopCapturing(); + videoDevice->close(); + } + +} + +void Webcam::askIncommingInvitation() +{ + m_direction = Incoming; + //protect, in case this is deleted when the messagebox is active + QGuardedPtr<Webcam> _this = this; + QString message= (m_who==wProducer) ? + i18n("<qt>The contact %1 wants to see <b>your</b> webcam, do you want them to see it?</qt>") : + i18n("The contact %1 wants to show you his/her webcam, do you want to see it?") ; + int result=KMessageBox::questionYesNo( 0L , message.arg(m_recipient), + i18n("Webcam invitation - Kopete MSN Plugin") , i18n("Accept") , i18n("Decline")); + if(!_this) + return; + + QString content = QString("SessionID: %1\r\n\r\n").arg(m_sessionId); + if(result==KMessageBox::Yes) + { + //Send two message, an OK, and an invite. + //Normaly, the user should decline the invite (i hope) + + // Send a 200 OK message to the recipient. + sendMessage(OK, content); + + + //send an INVITE message we want the user decline + //need to change the branch of the second message + m_branch=Uid::createUid(); + m_state = Negotiation; //set type to application/x-msnmsgr-transreqbody + + content=QString("Bridges: TRUDPv1 TCPv1\r\n" + "NetID: -1280904111\r\n" + "Conn-Type: Firewall\r\n" + "UPnPNat: false\r\n" + "ICF: false\r\n\r\n"); + + sendMessage(INVITE, content); + + } + else + { + //Decline the invitation + sendMessage(DECLINE, content); + m_state=Finished; + } +} + +void Webcam::sendBYEMessage() +{ + m_state=Finished; + QString content="Context: dAMAgQ==\r\n"; + sendMessage(BYE,content); + + //If ever the opposite client was dead or something, we'll ack anyway, so everything get cleaned + QTimer::singleShot(60*1000 , this, SLOT(acknowledged())); +} + + + +void Webcam::acknowledged() +{ + kdDebug(14140) << k_funcinfo << endl; + + switch(m_state) + { + case Invitation: + { +// m_state=Negotiation; + break; + } + + /* + case Negotiation: + { + if(m_type == UserDisplayIcon) + { + <<< Data preparation acknowledge message. + m_state = DataTransfer; + m_identifier++; + Start sending data. + slotSendData(); + } + break; + } + + case DataTransfer: + NOTE <<< Data acknowledged message. + <<< Bye message should follow. + if(m_type == File) + { + if(m_handshake == 0x01) + { + Data handshake acknowledge message. + Start sending data. + slotSendData(); + } + else if(m_handshake == 0x02) + { + Data acknowledge message. + Send the recipient a BYE message. + m_state = Finished; + sendMessage(BYE, "\r\n"); + } + } + + break; + */ + case Finished: + //BYE or DECLINE acknowledge message. + m_dispatcher->detach(this); + break; + default: + break; + } +} + + + + +void Webcam::processMessage(const Message& message) +{ + if(message.header.dataOffset+message.header.dataSize >= message.header.totalDataSize) + acknowledge( message ); //aknowledge if needed + + if(message.applicationIdentifier != 4l) + { + QString body = QCString(message.body.data(), message.header.dataSize); + kdDebug(14141) << k_funcinfo << "received, " << body << endl; + + if(body.startsWith("MSNSLP/1.0 200 OK")) + { + m_direction = Outgoing; + } + if(body.startsWith("INVITE")) + { + if(m_direction == Outgoing) + { + QRegExp regex(";branch=\\{([0-9A-F\\-]*)\\}\r\n"); + regex.search(body); + m_branch=regex.cap(1); + //decline + sendMessage(DECLINE); + makeSIPMessage("syn",0x17,0x2a,0x01); + } + } + else if(body.startsWith("MSNSLP/1.0 603 DECLINE")) + { + //if it is the declinaison of the second invite message, we have to don't care + //TODO anyway, if it's the declinaison of our invitation, we have to something + } + else if(body.startsWith("BYE")) + { + m_state = Finished; + + // Dispose of this transfer context. + m_dispatcher->detach(this); + } + return; + } + + + + //Let's take the fun, we entering into the delicious webcam negotiation binary protocol + + //well, there is maybe better to take utf16, but it's ascii, so no problem. + QByteArray dataMessage=message.body; + +#if 0 + QString echoS=""; + unsigned int f=0; + while(f<dataMessage.size()) + { + echoS+="\n"; + for(unsigned int q=0; q<16 ; q++) + { + if(q+f<dataMessage.size()) + { + unsigned int N=(unsigned int) (dataMessage[q+f]); + if(N<16) + echoS+="0"; + echoS+=QString::number( N ,16)+" "; + } + else + echoS+=" "; + } + echoS+=" "; + + for(unsigned int q=0; (q<16 && (q+f)<dataMessage.size()) ; q++) + { + unsigned char X=dataMessage[q+f]; + char C=((char)(( X<128 && X>31 ) ? X : '.')); + echoS+=QString::fromLatin1(&C,1); + } + f+=16; + } + kdDebug(14141) << k_funcinfo << dataMessage.size() << echoS << endl; +#endif + + + + + + for(uint pos=m_content.isNull() ? 10 : 0; pos<dataMessage.size(); pos+=2) + { + if(dataMessage[pos] !=0 ) + m_content+=dataMessage[pos]; + } + + if(message.header.dataOffset+message.header.dataSize < message.header.totalDataSize) + return; + + kdDebug(14141) << k_funcinfo << "Message contents: " << m_content << "\n" << endl; + if(m_content.startsWith("syn")) + { + if(m_direction == Incoming) + makeSIPMessage("syn",0x17,0x2a,0x01); + else + makeSIPMessage("ack",0xea,0x00,0x00); + } + else if(m_content.startsWith("ack")) + { + if(m_direction == Incoming) + makeSIPMessage("ack",0xea,0x00,0x00); + + if(m_who==wProducer) + { + uint sess=rand()%1000+5000; + uint rid=rand()%100+50; + m_myAuth=QString("recipientid=%1&sessionid=%2\r\n\r\n").arg(rid).arg(sess); + kdDebug(14140) << k_funcinfo << "m_myAuth= " << m_myAuth << endl; + QString producerxml=xml(sess , rid); + kdDebug(14140) << k_funcinfo << "producerxml= " << producerxml << endl; + makeSIPMessage(producerxml); + } + } + else if(m_content.contains("<producer>") || m_content.contains("<viewer>")) + { + QRegExp rx("<rid>([0-9]*)</rid>.*<session>([0-9]*)</session>"); + rx.search(m_content); + QString rid=rx.cap(1); + QString sess=rx.cap(2); + if(m_content.contains("<producer>")) + { + + QString viewerxml=xml(sess.toUInt() , rid.toUInt()); + kdDebug(14140) << k_funcinfo << "vewerxml= " << viewerxml << endl; + makeSIPMessage( viewerxml ,0x00,0x09,0x00 ); + m_peerAuth=m_myAuth=QString("recipientid=%1&sessionid=%2\r\n\r\n").arg(rid,sess); + kdDebug(14140) << k_funcinfo << "m_auth= " << m_myAuth << endl; + } + else + { + m_peerAuth=QString("recipientid=%1&sessionid=%2\r\n\r\n").arg(rid,sess); + + makeSIPMessage("receivedViewerData", 0xec , 0xda , 0x03); + } + + if(!m_listener) + { + //it should have been creed in xml + sendBYEMessage(); + return; + } + //m_listener->setResolutionEnabled(true); + // Create the callback that will try to accept incoming connections. + QObject::connect(m_listener, SIGNAL(readyAccept()), this, SLOT(slotAccept())); + QObject::connect(m_listener, SIGNAL(gotError(int)), this, SLOT(slotListenError(int))); + // Listen for incoming connections. + bool isListening = m_listener->listen(); + kdDebug(14140) << k_funcinfo << (isListening ? QString("listening %1").arg(m_listener->localAddress().toString()) : QString("not listening")) << endl; + + rx=QRegExp("<tcpport>([^<]*)</tcpport>"); + rx.search(m_content); + QString port1=rx.cap(1); + if(port1=="0") + port1=QString::null; + + rx=QRegExp("<tcplocalport>([^<]*)</tcplocalport>"); + rx.search(m_content); + QString port2=rx.cap(1); + if(port2==port1 || port2=="0") + port2=QString::null; + + rx=QRegExp("<tcpexternalport>([^<]*)</tcpexternalport>"); + rx.search(m_content); + QString port3=rx.cap(1); + if(port3==port1 || port3==port2 || port3=="0") + port3=QString::null; + + int an=0; + while(true) + { + an++; + if(!m_content.contains( QString("<tcpipaddress%1>").arg(an) )) + break; + rx=QRegExp(QString("<tcpipaddress%1>([^<]*)</tcpipaddress%2>").arg(an).arg(an)); + rx.search(m_content); + QString ip=rx.cap(1); + if(ip.isNull()) + continue; + + if(!port1.isNull()) + { + kdDebug(14140) << k_funcinfo << "trying to connect on " << ip <<":" << port1 << endl; + KBufferedSocket *sock=new KBufferedSocket( ip, port1, this ); + m_allSockets.append(sock); + QObject::connect( sock, SIGNAL( connected( const KResolverEntry&) ), this, SLOT( slotSocketConnected() ) ); + QObject::connect( sock, SIGNAL( gotError(int)), this, SLOT(slotSocketError(int))); + sock->connect(ip, port1); + kdDebug(14140) << k_funcinfo << "okok " << sock << " - " << sock->peerAddress().toString() << " ; " << sock->localAddress().toString() << endl; + } + if(!port2.isNull()) + { + kdDebug(14140) << k_funcinfo << "trying to connect on " << ip <<":" << port2 << endl; + KBufferedSocket *sock=new KBufferedSocket( ip, port2, this ); + m_allSockets.append(sock); + QObject::connect( sock, SIGNAL( connected( const KResolverEntry&) ), this, SLOT( slotSocketConnected() ) ); + QObject::connect( sock, SIGNAL( gotError(int)), this, SLOT(slotSocketError(int))); + sock->connect(ip, port2); + } + if(!port3.isNull()) + { + kdDebug(14140) << k_funcinfo << "trying to connect on " << ip <<":" << port3 << endl; + KBufferedSocket *sock=new KBufferedSocket( ip, port3, this ); + m_allSockets.append(sock); + QObject::connect( sock, SIGNAL( connected( const KResolverEntry&) ), this, SLOT( slotSocketConnected() ) ); + QObject::connect( sock, SIGNAL( gotError(int)), this, SLOT(slotSocketError(int))); + sock->connect(ip, port3); + } + } + QValueList<KBufferedSocket*>::iterator it; + for ( it = m_allSockets.begin(); it != m_allSockets.end(); ++it ) + { + KBufferedSocket *sock=(*it); + + //sock->enableRead( false ); + kdDebug(14140) << k_funcinfo << "connect to " << sock << " - "<< sock->peerAddress().toString() << " ; " << sock->localAddress().toString() << endl; + } + } + else if(m_content.contains("receivedViewerData")) + { + //I'm happy you received the xml i sent, really. + } + else + error(); + m_content=QString::null; +} + +void Webcam::makeSIPMessage(const QString &message, Q_UINT8 XX, Q_UINT8 YY , Q_UINT8 ZZ) +{ + QByteArray dataMessage; //(12+message.length()*2); + QDataStream writer(dataMessage, IO_WriteOnly); + writer.setByteOrder(QDataStream::LittleEndian); + writer << (Q_UINT8)0x80; + writer << (Q_UINT8)XX; + writer << (Q_UINT8)YY; + writer << (Q_UINT8)ZZ; + writer << (Q_UINT8)0x08; + writer << (Q_UINT8)0x00; + writer << message+'\0'; + //writer << (Q_UINT16)0x0000; + + /*QString echoS=""; + unsigned int f=0; + while(f<dataMessage.size()) + { + echoS+="\n"; + for(unsigned int q=0; q<16 ; q++) + { + if(q+f<dataMessage.size()) + { + unsigned int N=(unsigned int) (dataMessage[q+f]); + if(N<16) + echoS+="0"; + echoS+=QString::number( N ,16)+" "; + } + else + echoS+=" "; + } + echoS+=" "; + + for(unsigned int q=0; (q<16 && (q+f)<dataMessage.size()) ; q++) + { + unsigned char X=dataMessage[q+f]; + char C=((char)(( X<128 && X>31 ) ? X : '.')); + echoS+=QString::fromLatin1(&C,1); + } + f+=16; + } + kdDebug(14141) << k_funcinfo << dataMessage.size() << echoS << endl;*/ + + + sendBigP2PMessage(dataMessage); +} + +void Webcam::sendBigP2PMessage( const QByteArray & dataMessage) +{ + unsigned int size=m_totalDataSize=dataMessage.size(); + m_offset=0; + ++m_identifier; + + for(unsigned int f=0;f<size;f+=1200) + { + m_offset=f; + QByteArray dm2; + dm2.duplicate(dataMessage.data()+m_offset, QMIN(1200,m_totalDataSize-m_offset)); + sendData( dm2 ); + m_offset+=dm2.size(); + } + m_offset=0; + m_totalDataSize=0; +} + + + +QString Webcam::xml(uint session , uint rid) +{ + QString who= ( m_who == wProducer ) ? QString("producer") : QString("viewer"); + + QString ip; + + uint ip_number=1; + QStringList::iterator it; + QStringList ips=m_dispatcher->localIp(); + for ( it = ips.begin(); it != ips.end(); ++it ) + { + ip+=QString("<tcpipaddress%1>%2</tcpipaddress%3>").arg(ip_number).arg(*it).arg(ip_number); + ++ip_number; + } + + QString port = QString::number(getAvailablePort()); + + m_listener = new KServerSocket(port, this) ; + + return "<" + who + "><version>2.0</version><rid>"+QString::number(rid)+"</rid><udprid>"+QString::number(rid+1)+"</udprid><session>"+QString::number(session)+"</session><ctypes>0</ctypes><cpu>2931</cpu>" + + "<tcp><tcpport>"+port+"</tcpport>\t\t\t\t\t\t\t\t <tcplocalport>"+port+"</tcplocalport>\t\t\t\t\t\t\t\t <tcpexternalport>"+port+"</tcpexternalport>"+ip+"</tcp>"+ + "<udp><udplocalport>7786</udplocalport><udpexternalport>31863</udpexternalport><udpexternalip>"+ ip +"</udpexternalip><a1_port>31859</a1_port><b1_port>31860</b1_port><b2_port>31861</b2_port><b3_port>31862</b3_port><symmetricallocation>1</symmetricallocation><symmetricallocationincrement>1</symmetricallocationincrement><udpversion>1</udpversion><udpinternalipaddress1>127.0.0.1</udpinternalipaddress1></udp>"+ + "<codec></codec><channelmode>1</channelmode></"+who+">\r\n\r\n"; +} + +int Webcam::getAvailablePort() +{ + KConfig *config = KGlobal::config(); + config->setGroup( "MSN" ); + QString basePort=config->readEntry("WebcamPort"); + if(basePort.isEmpty() || basePort == "0" ) + basePort="6891"; + + uint firstport = basePort.toInt(); + uint maxOffset=config->readUnsignedNumEntry("WebcamMaxPortOffset", 10); + uint lastport = firstport + maxOffset; + + // try to find an available port + // + KServerSocket *ss = new KServerSocket(); + ss->setFamily(KResolver::InetFamily); + bool found = false; + unsigned int port = firstport; + for( ; port <= lastport; ++port) { + ss->setAddress( QString::number( port ) ); + bool success = ss->listen(); + if( found = ( success && ss->error() == KSocketBase::NoError ) ) + break; + ss->close(); + } + delete ss; + + + kdDebug(14140) << k_funcinfo<< "found available port : " << port << endl; + + return port; +} + + +/* ---------- Now functions about the dirrect connection --------- */ + +void Webcam::slotSocketConnected() +{ + kdDebug(14140) << k_funcinfo <<"##########################" << endl; + + m_webcamSocket=const_cast<KBufferedSocket*>(static_cast<const KBufferedSocket*>(sender())); + if(!m_webcamSocket) + return; + + kdDebug(14140) << k_funcinfo << "Connection established on " << m_webcamSocket->peerAddress().toString() << " ; " << m_webcamSocket->localAddress().toString() << endl; + + m_webcamSocket->setBlocking(false); + m_webcamSocket->enableRead(true); + m_webcamSocket->enableWrite(false); + + // Create the callback that will try to read bytes from the accepted socket. + QObject::connect(m_webcamSocket, SIGNAL(readyRead()), this, SLOT(slotSocketRead())); + // Create the callback that will try to handle the socket close event. + QObject::connect(m_webcamSocket, SIGNAL(closed()), this, SLOT(slotSocketClosed())); + // Create the callback that will try to handle the socket error event. +// QObject::connect(m_webcamSocket, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); + + m_webcamStates[m_webcamSocket]=wsConnected; + QCString to_send=m_peerAuth.utf8(); + m_webcamSocket->writeBlock(to_send.data(), to_send.length()); + kdDebug(14140) << k_funcinfo << "sending "<< m_peerAuth << endl; + +} + + +void Webcam::slotAccept() +{ + // Try to accept an incoming connection from the sending client. + m_webcamSocket = static_cast<KBufferedSocket*>(m_listener->accept()); + if(!m_webcamSocket) + { + // NOTE If direct connection fails, the sending + // client wil transfer the file data through the + // existing session. + kdDebug(14140) << k_funcinfo << "Direct connection failed." << endl; + // Close the listening endpoint. +// m_listener->close(); + return; + } + + kdDebug(14140) << k_funcinfo << "################################ Direct connection established." << endl; + + // Set the socket to non blocking, + // enable the ready read signal and disable + // ready write signal. + // NOTE readyWrite consumes too much cpu usage. + m_webcamSocket->setBlocking(false); + m_webcamSocket->enableRead(true); + m_webcamSocket->enableWrite(false); + + // Create the callback that will try to read bytes from the accepted socket. + QObject::connect(m_webcamSocket, SIGNAL(readyRead()), this, SLOT(slotSocketRead())); + // Create the callback that will try to handle the socket close event. + QObject::connect(m_webcamSocket, SIGNAL(closed()), this, SLOT(slotSocketClosed())); + // Create the callback that will try to handle the socket error event. + QObject::connect(m_webcamSocket, SIGNAL(gotError(int)), this, SLOT(slotSocketError(int))); + + m_allSockets.append(m_webcamSocket); + m_webcamStates[m_webcamSocket]=wsNegotiating; +} + +void Webcam::slotSocketRead() +{ + m_webcamSocket=const_cast<KBufferedSocket*>(static_cast<const KBufferedSocket*>(sender())); + + uint available = m_webcamSocket->bytesAvailable(); + kdDebug(14140) << k_funcinfo << m_webcamSocket << "############# " << available << " bytes available." << endl; + + QByteArray avail_buff(available); + m_webcamSocket->peekBlock(avail_buff.data(), avail_buff.size()); + + kdDebug(14140) << k_funcinfo << m_webcamSocket << avail_buff << endl; + + + + const QString connected_str("connected\r\n\r\n"); + switch(m_webcamStates[m_webcamSocket]) + { + case wsNegotiating: + { + if(available < m_myAuth.length()) + { + kdDebug(14140) << k_funcinfo << "waiting more data ( " << available << " of " <<m_myAuth.length()<< " )"<< endl; + break; + } + QByteArray buffer(available); + m_webcamSocket->readBlock(buffer.data(), buffer.size()); + + kdDebug(14140) << k_funcinfo << buffer.data() << endl; + + if(QString(buffer) == m_myAuth ) + { + closeAllOtherSockets(); + kdDebug(14140) << k_funcinfo << "Sending " << connected_str << endl; + QCString conne=connected_str.utf8(); + m_webcamSocket->writeBlock(conne.data(), conne.length()); + m_webcamStates[m_webcamSocket]=wsConnecting; + + //SHOULD NOT BE THERE + m_mimic=new MimicWrapper(); + if(m_who==wProducer) + { + Kopete::AV::VideoDevicePool *videoDevice = Kopete::AV::VideoDevicePool::self(); + videoDevice->open(); + videoDevice->setSize(320, 240); + videoDevice->startCapturing(); + + m_timerId=startTimer(m_timerFps); + kdDebug(14140) << k_funcinfo << "new timer" << m_timerId << endl; + } + m_widget=new MSNWebcamDialog(m_recipient); + connect(m_widget, SIGNAL( closingWebcamDialog() ) , this , SLOT(sendBYEMessage())); + + } + else + { + kdWarning(14140) << k_funcinfo << "Auth failed" << endl; + m_webcamSocket->disconnect(); + m_webcamSocket->deleteLater(); + m_allSockets.remove(m_webcamSocket); + m_webcamSocket=0l; + //sendBYEMessage(); + } + break; + } + case wsConnecting: + case wsConnected: + { + if(available < connected_str.length()) + { + kdDebug(14140) << k_funcinfo << "waiting more data ( " << available << " of " <<connected_str.length()<< " )"<< endl; + break; + } + QByteArray buffer(connected_str.length()); + m_webcamSocket->readBlock(buffer.data(), buffer.size()); + +// kdDebug(14140) << k_funcinfo << "state " << m_webcamState << " received :" << QCString(buffer) << endl; + + + if(QString(buffer) == connected_str) + { + if(m_webcamStates[m_webcamSocket]==wsConnected) + { + closeAllOtherSockets(); + kdDebug(14140) << k_funcinfo << "Sending " << connected_str << endl; + QCString conne=connected_str.utf8(); + m_webcamSocket->writeBlock(conne.data(), conne.length()); + + //SHOULD BE DONE IN ALL CASE + m_mimic=new MimicWrapper(); + if(m_who==wProducer) + { + Kopete::AV::VideoDevicePool *videoDevice = Kopete::AV::VideoDevicePool::self(); + videoDevice->open(); + videoDevice->setSize(320, 240); + videoDevice->startCapturing(); + + m_timerId=startTimer(m_timerFps); + kdDebug(14140) << k_funcinfo << "new timer" << m_timerId << endl; + } + m_widget=new MSNWebcamDialog(m_recipient); + connect(m_widget, SIGNAL( closingWebcamDialog() ) , this , SLOT(sendBYEMessage())); + + } + m_webcamStates[m_webcamSocket]=wsTransfer; + + } + else + { + kdWarning(14140) << k_funcinfo << "Connecting failed" << endl; + m_webcamSocket->disconnect(); + m_webcamSocket->deleteLater(); + m_allSockets.remove(m_webcamSocket); + m_webcamSocket=0l; + } + break; + } + case wsTransfer: + { + if(m_who==wProducer) + { + kdWarning(14140) << k_funcinfo << "data received when we are producer"<< endl; + break; + } + if(available < 24) + { + kdDebug(14140) << k_funcinfo << "waiting more data ( " << available << " of " <<24<< " )"<< endl; + break; + } + QByteArray buffer(24); + m_webcamSocket->peekBlock(buffer.data(), buffer.size()); + + Q_UINT32 paysize=(uchar)buffer[8] + ((uchar)buffer[9]<<8) + ((uchar)buffer[10]<<16) + ((uchar)buffer[11]<<24); + + if(available < (paysize+24)) + { + kdDebug(14140) << k_funcinfo << "waiting more data ( " << available << " of " <<paysize<< " )"<< endl; + break; + } + m_webcamSocket->readBlock(buffer.data(), 24); //flush + buffer.resize(paysize); + m_webcamSocket->readBlock(buffer.data(), buffer.size()); + + QPixmap pix=m_mimic->decode(buffer); + if(pix.isNull()) + { + kdWarning(14140) << k_funcinfo << "incorrect pixmap returned, better to stop everything"<< endl; + m_webcamSocket->disconnect(); + sendBYEMessage(); + } + m_widget->newImage(pix); + break; + } + default: + break; + } + +} + +void Webcam::slotListenError(int errorCode) +{ + kdWarning(14140) << k_funcinfo << "Error " << errorCode << " : " << m_listener->errorString() << endl; +} + +void Webcam::slotSocketClosed() +{ + if(!m_dispatcher) //we are in this destructor + return; + + KBufferedSocket *m_webcamSocket=const_cast<KBufferedSocket*>(static_cast<const KBufferedSocket*>(sender())); + + kdDebug(14140) << k_funcinfo << m_webcamSocket << endl; + + if(m_listener) + { //if we are still waiting for other socket to connect, just remove this socket from the socket list + m_webcamSocket->disconnect(); + m_webcamSocket->deleteLater(); + m_allSockets.remove(m_webcamSocket); + m_webcamSocket=0l; + } + else // else, close the session + sendBYEMessage(); + +} + +void Webcam::slotSocketError(int errorCode) +{ + KBufferedSocket *socket=const_cast<KBufferedSocket*>(static_cast<const KBufferedSocket*>(sender())); + kdDebug(14140) << k_funcinfo << socket << " - " << errorCode << " : " << socket->errorString() << endl; + //sendBYEMessage(); +} + +void Webcam::closeAllOtherSockets() +{ + //m_lisener->close(); + delete m_listener; + m_listener=0l; + + QValueList<KBufferedSocket*>::iterator it; + for ( it = m_allSockets.begin(); it != m_allSockets.end(); ++it ) + { + KBufferedSocket *sock=(*it); + if(sock != m_webcamSocket) + delete sock; + } + m_allSockets.clear(); +} + + +void Webcam::timerEvent( QTimerEvent *e ) +{ + if(e->timerId() != m_timerId) + return TransferContext::timerEvent(e); + +// kdDebug(14140) << k_funcinfo << endl; + + Kopete::AV::VideoDevicePool *videoDevice = Kopete::AV::VideoDevicePool::self(); + videoDevice->getFrame(); + QImage img; + videoDevice->getImage(&img); + + if(m_widget) + m_widget->newImage(img); + + if(img.width()!=320 || img.height()!=240) + { + kdWarning(14140) << k_funcinfo << "Bad image size " <<img.width() << "x" << img.height() << endl; + return; + } + + uchar *bits=img.bits(); + QByteArray image_data(img.width()*img.height()*3); + uint b2=0; + uint imgsize=img.width()*img.height()*4; + for(uint f=0; f< imgsize; f+=4) + { + image_data[b2+0]=bits[f+2]; + image_data[b2+1]=bits[f+1]; + image_data[b2+2]=bits[f+0]; + b2+=3; + } + + QByteArray frame=m_mimic->encode(image_data); + + + kdDebug(14140) << k_funcinfo << "Sendinf frame of size " << frame.size() << endl; + //build the header. + QByteArray header; + + QDataStream writer(header, IO_WriteOnly); + writer.setByteOrder(QDataStream::LittleEndian); + writer << (Q_UINT16)24; // header size + writer << (Q_UINT16)img.width(); + writer << (Q_UINT16)img.height(); + writer << (Q_UINT16)0x0000; //wtf .? + writer << (Q_UINT32)frame.size(); + writer << (Q_UINT8)('M') << (Q_UINT8)('L') << (Q_UINT8)('2') << (Q_UINT8)('0'); + writer << (Q_UINT32)0x00000000; //wtf .? + writer << QTime::currentTime(); //FIXME: possible midnight bug ? + + m_webcamSocket->writeBlock(header.data(), header.size()); + m_webcamSocket->writeBlock(frame.data(), frame.size()); +} + + +} + + +#include "webcam.moc" + +#endif + diff --git a/kopete/protocols/msn/webcam.h b/kopete/protocols/msn/webcam.h new file mode 100644 index 00000000..4dc72fae --- /dev/null +++ b/kopete/protocols/msn/webcam.h @@ -0,0 +1,91 @@ +/* + Copyright (c) 2005 by Olivier Goffart <ogoffart@ kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef P2PWEBCAM_H +#define P2PWEBCAM_H + +#include "p2p.h" + +#if MSN_WEBCAM + +namespace KNetwork{ class KServerSocket; class KBufferedSocket; } + +class MimicWrapper; +class QLabel; +class MSNWebcamDialog; +class QTimerEvent; + +namespace P2P { + + +class Webcam : public TransferContext +{ Q_OBJECT + public: + enum Who { wProducer , wViewer }; + + Webcam( Who who , const QString& to, Dispatcher *parent, Q_UINT32 sessionID); + ~Webcam( ); + + virtual void processMessage(const Message& message); + + public slots: + void askIncommingInvitation(); + virtual void acknowledged(); + void sendBYEMessage(); + + private: + void makeSIPMessage(const QString &message, Q_UINT8 XX=0, Q_UINT8 YY=9 , Q_UINT8 ZZ=0); + void sendBigP2PMessage( const QByteArray& dataMessage ); + void closeAllOtherSockets(); + QString m_content; + + QString xml(uint session , uint rid); + int getAvailablePort(); + + + KNetwork::KServerSocket *m_listener; + KNetwork::KBufferedSocket *m_webcamSocket; + + enum WebcamStatus { wsNegotiating , wsConnecting, wsConnected, wsTransfer } ; + + Who m_who; + + QString m_myAuth; + QString m_peerAuth; + + MimicWrapper *m_mimic; + MSNWebcamDialog *m_widget; + + QValueList<KNetwork::KBufferedSocket* > m_allSockets; + QMap<KNetwork::KBufferedSocket*, WebcamStatus> m_webcamStates; + + int m_timerId; + int m_timerFps; + + private slots: + void slotListenError(int errorCode); + void slotAccept(); + void slotSocketRead(); + void slotSocketClosed(); + void slotSocketError(int errorCode); + void slotSocketConnected(); +// void slotReadyWrite(); + protected: + virtual void timerEvent( QTimerEvent * ); +}; + +} + +#endif + +#endif diff --git a/kopete/protocols/msn/webcam/Makefile.am b/kopete/protocols/msn/webcam/Makefile.am new file mode 100644 index 00000000..27d37fe0 --- /dev/null +++ b/kopete/protocols/msn/webcam/Makefile.am @@ -0,0 +1,14 @@ +METASOURCES = AUTO +SUBDIRS = libmimic +AM_CPPFLAGS = -I$(srcdir)/libmimic \ + $(KOPETE_INCLUDES) \ + $(all_includes) \ + $(GLIB_CFLAGS) + +noinst_LTLIBRARIES = libmimicwrapper.la + +libmimicwrapper_la_SOURCES = mimicwrapper.cpp msnwebcamdialog.cpp +libmimicwrapper_la_LIBADD = ./libmimic/libmimic.la +libmimicwrapper_la_LDFLAGS = -no-undefined $(GLIB_LIBS) $(all_libraries) + + diff --git a/kopete/protocols/msn/webcam/libmimic/AUTHORS b/kopete/protocols/msn/webcam/libmimic/AUTHORS new file mode 100644 index 00000000..8dd0671d --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/AUTHORS @@ -0,0 +1,2 @@ +Ole André Vadla Ravnås <oleavr@gmail.com> + diff --git a/kopete/protocols/msn/webcam/libmimic/COPYING b/kopete/protocols/msn/webcam/libmimic/COPYING new file mode 100644 index 00000000..b1e3f5a2 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/kopete/protocols/msn/webcam/libmimic/Makefile.am b/kopete/protocols/msn/webcam/libmimic/Makefile.am new file mode 100644 index 00000000..1a2c99d3 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/Makefile.am @@ -0,0 +1,24 @@ +# INCLUDES = @GLIB_CFLAGS@ +AM_CPPFLAGS = $(all_includes) $(GLIB_CFLAGS) + +# libmimicincludedir = $(includedir) +# libmimicinclude_HEADERS = mimic.h + +noinst_LTLIBRARIES = libmimic.la +libmimic_la_SOURCES = \ + mimic.c \ + encode.c \ + decode.c \ + bitstring.c \ + vlc_common.c \ + vlc_encode.c \ + vlc_decode.c \ + fdct_quant.c \ + idct_dequant.c \ + colorspace.c \ + deblock.c \ + mimic-private.h +# libmimic_la_LDFLAGS = \ +# -version-info $(MIMIC_CURRENT):$(MIMIC_REVISION):$(MIMIC_AGE) \ +# -export-symbols-regex "^[^_].*" + diff --git a/kopete/protocols/msn/webcam/libmimic/README b/kopete/protocols/msn/webcam/libmimic/README new file mode 100644 index 00000000..c60336ec --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/README @@ -0,0 +1,40 @@ +ABOUT +----- + +libmimic is an open source video encoding/decoding library for Mimic V2.x- +encoded content (fourCC: ML20), which is the encoding used by MSN Messenger +for webcam conversations. + +It was written because there was no third-party MSN-client that supported +this feature due to this proprietary/unknown codec involved. I didn't like +this lack of interoperability, so I decided to do something about it. After +studying the official MSN-client a little closer, it became clear that the +codec involved was statically linked into the executable, so there was no +easy way to use the codec code through Wine. So for fun, and challenge, I +reverse-engineered the original implementation by studying the massive +amount of assembly code involved, and after a lot of hard work I ended +up with this implementation in C. + +It should be noted that reverse-engineering for interoperability is 100% +legal here in Norway (and in most European countries). + + +THANKS +------ + +Special thanks to Rob Taylor and the rest of the Farsight-team for all +the feedback and inspiration during development, you guys rock! :-) + + +BOTTOM LINE +----------- + +If you like my work and decide to use it in your project, please feel free +to credit me. I put a lot of time and hard work into this, so I hope others +will find it useful. + +Well, enough chit chat, enjoy! :-) + +Ole André Vadla Ravnås +oleavr at gmail dot com + diff --git a/kopete/protocols/msn/webcam/libmimic/bitstring.c b/kopete/protocols/msn/webcam/libmimic/bitstring.c new file mode 100644 index 00000000..2aef7284 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/bitstring.c @@ -0,0 +1,88 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "mimic-private.h" + +/* + * _read_bits + * + * Internal helper-function used to read num_bits + * from stream. + */ +guint32 _read_bits(MimCtx *ctx, gint num_bits) +{ + guint32 bits; + + if (ctx->cur_chunk_len >= 16) { + guchar *input_buf = (guchar *) ctx->data_buffer + ctx->data_index; + + if (!ctx->read_odd) { + ctx->read_odd = TRUE; + + ctx->cur_chunk = (input_buf[3] << 24) | + (input_buf[2] << 16) | + (input_buf[1] << 8) | + input_buf[0]; + + } else { + ctx->read_odd = FALSE; + + ctx->cur_chunk = (input_buf[1] << 24) | + (input_buf[0] << 16) | + (input_buf[7] << 8) | + input_buf[6]; + + ctx->data_index += 4; + } + + ctx->cur_chunk_len -= 16; + } + + bits = (ctx->cur_chunk << ctx->cur_chunk_len) >> (32 - num_bits); + ctx->cur_chunk_len += num_bits; + + return bits; +} + +/* + * _write_bits + * + * Internal helper-function used to write "length" + * bits of "bits" to stream. + */ +void _write_bits(MimCtx *ctx, guint32 bits, gint length) +{ + /* Left-align the bit string within its 32-bit container. */ + bits <<= (32 - length); + + /* Append the bit string (one or more of the trailing bits might not fit, but that's ok). */ + ctx->cur_chunk |= bits >> ctx->cur_chunk_len; + ctx->cur_chunk_len += length; + + /* Is it full? */ + if (ctx->cur_chunk_len >= 32) { + + /* Add the full 32-bit chunk to the stream and update counter. */ + ctx->chunk_ptr[0] = GUINT32_TO_LE(ctx->cur_chunk); + ctx->chunk_ptr++; + ctx->cur_chunk_len -= 32; + + /* Add any trailing bits that didn't fit. */ + ctx->cur_chunk = bits << (length - ctx->cur_chunk_len); + } +} + diff --git a/kopete/protocols/msn/webcam/libmimic/colorspace.c b/kopete/protocols/msn/webcam/libmimic/colorspace.c new file mode 100644 index 00000000..620992c6 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/colorspace.c @@ -0,0 +1,161 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "mimic-private.h" + +#define RED_INDEX_1 0 +#define GREEN_INDEX_1 1 +#define BLUE_INDEX_1 2 + +#define RED_INDEX_2 3 +#define GREEN_INDEX_2 4 +#define BLUE_INDEX_2 5 + +/* + * _rgb_to_yuv + * + * Internal helper-function used to convert an image + * from RGB 24-bpp packed-pixel to YUV420 planar. + */ +void _rgb_to_yuv(const guchar *input_rgb, + guchar *output_y, + guchar *output_cb, + guchar *output_cr, + gint width, + gint height) +{ + gint y, x; + + for (y = 0; y < height; y += 2) { + + const guchar *src1, *src2; + guchar *dst1, *dst2, *dst3, *dst4; + gint num_cols; + + src1 = input_rgb + ((height - 1 - y) * width * 3); + src2 = input_rgb + ((height - 2 - y) * width * 3); + + dst1 = output_y + (y * width); + dst2 = output_y + ((y + 1) * width); + dst3 = output_cb + ((y / 2) * (width / 2)); + dst4 = output_cr + ((y / 2) * (width / 2)); + + num_cols = width / 2; + + for (x = 0; x < num_cols; x++) { + + gint expr1, expr2, expr3, expr4, expr5, v; + + expr1 = (src1[BLUE_INDEX_1] * 19595) + (src1[GREEN_INDEX_1] * 38470) + (src1[RED_INDEX_1] * 7471); + expr2 = (src1[BLUE_INDEX_2] * 19595) + (src1[GREEN_INDEX_2] * 38470) + (src1[RED_INDEX_2] * 7471); + expr3 = (src2[BLUE_INDEX_1] * 19595) + (src2[GREEN_INDEX_1] * 38470) + (src2[RED_INDEX_1] * 7471); + expr4 = (src2[BLUE_INDEX_2] * 19595) + (src2[GREEN_INDEX_2] * 38470) + (src2[RED_INDEX_2] * 7471); + + expr5 = expr1 + expr2 + expr3 + expr4; + + dst1[0] = expr1 >> 16; + dst1[1] = expr2 >> 16; + dst2[0] = expr3 >> 16; + dst2[1] = expr4 >> 16; + + v = (((src1[BLUE_INDEX_1] + src1[BLUE_INDEX_2] + src2[BLUE_INDEX_1] + src2[BLUE_INDEX_2]) << 16) - expr5 + 131071) >> 16; + dst3[0] = _clamp_value(((v * 57475) >> 18) + 128); + + v = (((src1[RED_INDEX_1] + src1[RED_INDEX_2] + src2[RED_INDEX_1] + src2[RED_INDEX_2]) << 16) - expr5 + 131071) >> 16; + dst4[0] = ((v * 32244) >> 18) + 128; + + src1 += 6; + src2 += 6; + + dst1 += 2; + dst2 += 2; + dst3++; + dst4++; + + } + + } + +} + +/* + * _yuv_to_rgb + * + * Internal helper-function used to convert an image + * from YUV420 planar to RGB 24-bpp packed-pixel. + */ +void _yuv_to_rgb(const guchar *input_y, + const guchar *input_cb, + const guchar *input_cr, + guchar *output_rgb, + guint width, + guint height) +{ + const guchar *src_y, *src_cb, *src_cr; + guchar *dst_rgb; + guint i, j, rgb_stride; + + src_y = input_y; + src_cb = input_cb; + src_cr = input_cr; + + rgb_stride = width * 3; + dst_rgb = output_rgb + (rgb_stride * (height - 1)); + + for (i = 0; i < height; i++) { + const guchar *p_y, *p_cb, *p_cr; + guchar *p_rgb; + + p_y = src_y; + p_cb = src_cb; + p_cr = src_cr; + + p_rgb = dst_rgb; + + for (j = 0; j < width; j++) { + gint v; + + v = ((p_y[0] * 65536) + ((p_cr[0] - 128) * 133169)) / 65536; + p_rgb[0] = _clamp_value(v); + + v = ((p_y[0] * 65536) - ((p_cr[0] - 128) * 25821) - ((p_cb[0] - 128) * 38076)) / 65536; + p_rgb[1] = _clamp_value(v); + + v = ((p_y[0] * 65536) + ((p_cb[0] - 128) * 74711)) / 65536; + p_rgb[2] = _clamp_value(v); + + p_y++; + if ((j + 1) % 2 == 0) { + p_cb++; + p_cr++; + } + + p_rgb += 3; + } + + src_y += width; + if ((i + 1) % 2 == 0) { + src_cb += (width + 1) / 2; + src_cr += (width + 1) / 2; + } + + dst_rgb -= rgb_stride; + + } + +} + diff --git a/kopete/protocols/msn/webcam/libmimic/deblock.c b/kopete/protocols/msn/webcam/libmimic/deblock.c new file mode 100644 index 00000000..cfd6ff6d --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/deblock.c @@ -0,0 +1,450 @@ +/* Copyright (C) 2005 Ole Andr Vadla Ravns <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include <string.h> +#include "mimic-private.h" + +static void deblock_horizontal(guchar *blocks, guint stride, guint row_count); +static void deblock_vertical(guchar *blocks, guint stride, guint row_count); + +static gboolean deblock_h_consider_entire(guchar *blocks, guint stride); +static void deblock_h_do_entire(guchar *blocks, guint stride); +static void deblock_h_do_boundaries(guchar *blocks, guint stride); + +static gboolean deblock_v_consider_entire(guchar *blocks, guint stride); +static void deblock_v_do_entire(guchar *blocks, guint stride); +static void deblock_v_do_boundaries(guchar *blocks, guint stride); + +/* + * _deblock + * + * Internal helper-function used for de-blocking. + */ +void _deblock(guchar *blocks, guint stride, guint row_count) +{ + deblock_horizontal(blocks, stride, row_count); + deblock_vertical(blocks, stride, row_count); +} + +static void deblock_horizontal(guchar *blocks, guint stride, guint row_count) +{ + guchar *p1; + gint i, j, n1, n2; + + if (stride <= 8 || row_count == 0) + return; + + p1 = blocks + 4; + n1 = ((row_count - 1) >> 2) + 1; + n2 = ((stride - 9) >> 3) + 1; + + for (i = 0; i < n1; i++) { + guchar *p; + + p = p1; + + for (j = 0; j < n2; j++) { + + if (deblock_h_consider_entire(p - 1, stride) == TRUE) { + + gint v1, v2, v; + + v1 = p[0]; + v2 = p[7]; + + v = v1 - v2; + if (v <= 0) + v = v2 - v1; + + if (v < 20) + deblock_h_do_entire(p - 1, stride); + + } else { + deblock_h_do_boundaries(p - 1, stride); + } + + p += 8; + } + + p1 += stride * 4; + } +} + +static void deblock_vertical(guchar *blocks, guint stride, guint row_count) +{ + gint i, j, k, n1, n2; + guchar *p1, *p2; + + if (stride == 0 || row_count <= 8) + return; + + p1 = blocks + (stride * 3); + p2 = blocks + (stride * 4); + + n1 = ((row_count - 9) >> 3) + 1; + n2 = ((stride - 1) >> 3) + 1; + + for (i = 0; i < n1; i++) { + guchar *p3, *p4; + + p3 = p1; + p4 = p2; + + for (j = 0; j < n2; j++) { + + if (deblock_v_consider_entire(p3, stride) == TRUE) { + guchar *p5; + gboolean do_entire; + + p5 = p3 + (stride * 8); + do_entire = TRUE; + + for (k = 0; k < 8; k++) { + gint v1, v2, v; + + v1 = p4[k]; + v2 = p5[k]; + + v = v1 - v2; + if (v <= 0) + v = v2 - v1; + + if (v > 20) { + do_entire = FALSE; + break; + } + } + + if (do_entire) + deblock_v_do_entire(p3, stride); + } else { + deblock_v_do_boundaries(p3, stride); + } + + p3 += 8; + p4 += 8; + } + + p1 += stride * 8; + p2 += stride * 8; + } +} + +static gboolean deblock_h_consider_entire(guchar *blocks, guint stride) +{ + guchar *p; + gint i, j, count; + + count = 0; + p = blocks; + + for (i = 0; i < 4; i++) { + + for (j = 1; j <= 7; j++) { + gint v1, v2, v; + + v1 = p[j]; + v2 = p[j+1]; + + v = v1 - v2; + if (v <= 0) + v = v2 - v1; + + if (v <= 1) + count--; + } + + p += stride; + } + + return (count <= -20); +} + +static void deblock_h_do_entire(guchar *blocks, guint stride) +{ + guchar buf[8], *p; + gint i; + + p = blocks; + + for (i = 0; i < 4; i++) { + gint v, low, high; + + v = p[0] - p[1]; + if (v <= 0) + v = p[1] - p[0]; + + if (v < 10) + low = p[0]; + else + low = p[1]; + + v = p[8] - p[9]; + if (v <= 0) + v = p[9] - p[8]; + + if (v >= 10) + high = p[8]; + else + high = p[9]; + + v = (low * 3) + p[1] + p[2] + p[3] + p[4] + 4; + buf[0] = (((p[1] + v) << 1) - p[4] + p[5]) >> 4; + + v += p[5] - low; + buf[1] = (((p[2] + v) << 1) - p[5] + p[6]) >> 4; + + v += p[6] - low; + buf[2] = (((p[3] + v) << 1) - p[6] + p[7]) >> 4; + + v += p[7] - low; + buf[3] = (((p[4] + v) << 1) - p[1] - p[7] + p[8] + low) >> 4; + + v += p[8] - p[1]; + buf[4] = (((p[5] + v) << 1) + p[1] - p[2] - p[8] + high) >> 4; + + v += high - p[2]; + buf[5] = (((p[6] + v) << 1) + p[2] - p[3]) >> 4; + + v += high - p[3]; + buf[6] = (((p[7] + v) << 1) + p[3] - p[4]) >> 4; + + v += high; + buf[7] = (((p[8] + v) << 1) - p[4] - p[5]) >> 4; + + memcpy(p + 1, buf, 8); + + p += stride; + } +} + +static void deblock_h_do_boundaries(guchar *blocks, guint stride) +{ + guchar *p; + gint i; + + p = blocks; + + for (i = 0; i < 4; i++) { + gint v, v1, v2, v3; + + v = p[4] - p[5]; + + if ((v / 2) != 0) { + + v1 = ((p[3] - p[6]) * 2) - (v * 5); + + if (abs(v1) < 80) { + + v2 = ((p[3] - p[2]) * 5) + ((p[1] - p[4]) * 2); + v3 = (p[5] * 2) + (p[7] * 5) - (p[8] * 7); + + v = abs(v1) - MIN(abs(v2), abs(v3)); + + if (v > 0) { + + v = ((v * 5) + 32) >> 6; + if (v > 0) { + + v2 = (p[4] - p[5]) / 2; + v3 = (((v1 < 0) * 2) - 1) * v; + + if (v2 > 0) + v = MIN(v2, ((v3 < 0) - 1) & v3); + else + v = MAX(v2, ((v3 > 0) - 1) & v3); + + p[4] -= v; + p[5] += v; + } + } + } + } + + p += stride; + } +} + +static gboolean deblock_v_consider_entire(guchar *blocks, guint stride) +{ + gint count, i, j; + guchar *p1, *p2; + + count = 0; + + p1 = blocks + stride; + p2 = blocks + (stride * 2); + + for (i = 0; i < 7; i++) { + + for (j = 0; j < 8; j++) { + gint v1, v2, v; + + v1 = p1[j]; + v2 = p2[j]; + + v = v1 - v2; + if (v <= 0) + v = v2 - v1; + + if (v <= 1) + count++; + } + + p1 += stride; + p2 += stride; + } + + return (count > 40); +} + +static void deblock_v_do_entire(guchar *blocks, guint stride) +{ + gint offset0, offset1, offset2, offset3; + gint offset4, offset5, offset6, offset7; + gint offset8, i; + guchar *p, buf[8]; + + offset0 = stride - (stride * 6); + offset1 = (stride * 2) - (stride * 6); + offset2 = (stride * 3) - (stride * 6); + offset3 = (stride * 4) - (stride * 6); + offset4 = (stride * 5) - (stride * 6); + offset5 = 0; + offset6 = (stride * 7) - (stride * 6); + offset7 = (stride * 8) - (stride * 6); + offset8 = (stride * 9) - (stride * 6); + + p = blocks + (stride * 6); + + for (i = 0; i < 8; i++) { + gint v, low, high; + + v = blocks[i] - p[offset0]; + if (v <= 0) + v = p[offset0] - blocks[i]; + + if (v < 10) + low = blocks[i]; + else + low = p[offset0]; + + v = p[offset7] - p[offset8]; + if (v <= 0) + v = p[offset8] - p[offset7]; + + if (v < 10) + high = p[offset8]; + else + high = p[offset7]; + + v = p[offset0] + (low * 3) + p[offset1] + p[offset2] + p[offset3] + 4; + + buf[0] = (((p[offset0] + v) << 1) - p[offset3] + p[offset4]) >> 4; + + v += p[offset4] - low; + + buf[1] = (((p[offset1] + v) << 1) - p[offset4] + p[0]) >> 4; + + v += p[0] - low; + + buf[2] = (((p[offset2] + v) << 1) - p[0] + p[offset6]) >> 4; + + v += p[offset6] - low; + + buf[3] = (((p[offset3] + v) << 1) - p[offset0] - p[offset6] + p[offset7] + low) >> 4; + + v += p[offset7] - p[offset0]; + + buf[4] = (((p[offset4] + v) << 1) - p[offset7] - p[offset1] + p[offset0] + high) >> 4; + + v += high - p[offset1]; + + buf[5] = (((p[0] + v) << 1) - p[offset2] + p[offset1]) >> 4; + + v += high - p[offset2]; + + buf[6] = (((p[offset6] + v) << 1) - p[offset3] + p[offset2]) >> 4; + + v += high; + + buf[7] = (((p[offset7] + v) << 1) - p[offset4] - p[offset3]) >> 4; + + p[offset0] = buf[0]; + p[offset1] = buf[1]; + p[offset2] = buf[2]; + p[offset3] = buf[3]; + p[offset4] = buf[4]; + p[offset5] = buf[5]; + p[offset6] = buf[6]; + p[offset7] = buf[7]; + + p++; + } +} + +static void deblock_v_do_boundaries(guchar *blocks, guint stride) +{ + guchar *p; + gint offset0, offset1, offset2, offset3; + gint offset4, offset5, offset6, offset7; + gint i; + + p = blocks + (stride * 3); + + offset0 = stride - (stride * 3); + offset1 = (stride * 2) - (stride * 3); + offset2 = 0; + offset3 = (stride * 4) - (stride * 3); + offset4 = (stride * 5) - (stride * 3); + offset5 = (stride * 6) - (stride * 3); + offset6 = (stride * 7) - (stride * 3); + offset7 = (stride * 8) - (stride * 3); + + for (i = 0; i < 8; i++) { + gint v1, v2, v3, v; + + v1 = ((p[offset4] - p[offset3]) * 5) + ((p[offset2] - p[offset5]) * 2); + + if (abs(v1) < 80) { + + v2 = ((p[offset2] - p[offset1]) * 5) + ((p[offset0] - p[offset3]) * 2); + v3 = ((p[offset6] - p[offset5]) * 5) + ((p[offset4] - p[offset7]) * 2); + + v = abs(v1) - MIN(abs(v2), abs(v3)); + if (v < 0) + v = 0; + + v2 = (p[offset3] - p[offset4]) / 2; + v3 = (((v * 5) + 32) >> 6) * (((v1 < 0) * 2) - 1); + + if (v2 > 0) + v = MIN(v2, ((v3 < 0) - 1) & v3); + else + v = MAX(v2, ((v3 > 0) - 1) & v3); + } else { + v = 0; + } + + p[offset3] -= v; + p[offset4] += v; + + p++; + } +} + diff --git a/kopete/protocols/msn/webcam/libmimic/decode.c b/kopete/protocols/msn/webcam/libmimic/decode.c new file mode 100644 index 00000000..2bc0e6c9 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/decode.c @@ -0,0 +1,311 @@ +/* Copyright (C) 2005 Ole Andr Vadla Ravns <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string.h> +#include "mimic-private.h" + + +static gboolean decode(MimCtx *ctx, gboolean is_pframe); + +/** + * Decode a MIMIC-encoded frame into RGB data. + * + * @param ctx the mimic context + * @param input_buffer buffer containing the MIMIC-encoded frame to decode + * @param output_buffer buffer that will receive the decoded frame in RGB 24-bpp packed pixel top-down format + * (use #mimic_get_property to determine the required buffer size, as well as frame width and height) + * @returns #TRUE on success + */ +gboolean mimic_decode_frame(MimCtx *ctx, + const guchar *input_buffer, + guchar *output_buffer) +{ + gboolean result, is_pframe; + guchar *input_y, *input_cr, *input_cb; + gint width, height; + + /* + * Some sanity checks. + */ + if (ctx == NULL || input_buffer == NULL || output_buffer == NULL) + { + return FALSE; + } + + if (!ctx->decoder_initialized) + { + return FALSE; + } + + /* + * Get frame dimensions. + */ + width = GUINT16_FROM_LE(*((guint16 *) (input_buffer + 4))); + height = GUINT16_FROM_LE(*((guint16 *) (input_buffer + 6))); + + /* + * Resolution changing is not supported. + */ + if (width != ctx->frame_width || + height != ctx->frame_height) + { + return FALSE; + } + + /* + * Increment frame counter. + */ + ctx->frame_num++; + + /* + * Initialize state. + */ + ctx->quality = GUINT16_FROM_LE(*((guint16 *) (input_buffer + 2))); + is_pframe = GUINT32_FROM_LE(*((guint32 *) (input_buffer + 12))); + ctx->num_coeffs = input_buffer[16]; + + ctx->data_buffer = (gchar *) (input_buffer + 20); + ctx->data_index = 0; + ctx->cur_chunk_len = 16; + ctx->read_odd = FALSE; + + /* + * Decode frame. + */ + if (!(is_pframe && ctx->prev_frame_buf == NULL)) + result = decode(ctx, is_pframe); + else + { + result = FALSE; + } + + /* + * Perform YUV 420 to RGB conversion. + */ + input_y = ctx->cur_frame_buf; + input_cr = ctx->cur_frame_buf + ctx->y_size; + input_cb = ctx->cur_frame_buf + ctx->y_size + ctx->crcb_size; + + _yuv_to_rgb(input_y, + input_cb, + input_cr, + output_buffer, + ctx->frame_width, + ctx->frame_height); + + return result; +} + +/* + * decode_main + * + * Main decoding loop. + */ +static gboolean decode(MimCtx *ctx, gboolean is_pframe) +{ + gint y, x, i, j, chrom_ch, *bptr, base_offset, offset; + gint dct_block[64]; + guchar *src, *dst, *p; + guint32 bit; + + /* + * Clear Cr and Cb planes. + */ + p = ctx->cur_frame_buf + ctx->y_size; + memset(p, 128, 2 * ctx->crcb_size); + + /* + * Decode Y plane. + */ + for (y = 0; y < ctx->num_vblocks_y; y++) { + + base_offset = ctx->y_stride * 8 * y; + + src = ctx->prev_frame_buf + base_offset; + dst = ctx->cur_frame_buf + base_offset; + + for (x = 0; x < ctx->num_hblocks_y; x++) { + + /* Check for a change condition in the current block. */ + + if (is_pframe) + bit = _read_bits(ctx, 1); + else + bit = 0; + + if (bit == 0) { + + /* Yes: Is the new content the same as it was in one of + * the 15 last frames preceding the previous? */ + + if (is_pframe) + bit = _read_bits(ctx, 1); + + if (bit == 0) { + + /* No: decode it. */ + + if (_vlc_decode_block(ctx, dct_block, ctx->num_coeffs) == FALSE) { + + return FALSE; + } + + _idct_dequant_block(ctx, dct_block, 0); + + bptr = dct_block; + for (i = 0; i < 8; i++) { + offset = ctx->y_stride * i; + + for (j = 0; j < 8; j++) { + guint v; + + if (bptr[j] <= 255) + v = (bptr[j] >= 0) ? bptr[j] : 0; + else + v = 255; + + *(dst + offset + j) = v; + } + + bptr += 8; + } + } else { + guint32 backref; + + /* Yes: read the backreference (4 bits) and copy. */ + + backref = _read_bits(ctx, 4); + + p = ctx->buf_ptrs[(ctx->ptr_index + backref) % 16]; + p += base_offset + (x * 8); + + for (i = 0; i < 8; i++) { + offset = ctx->y_stride * i; + + memcpy(dst + offset, p + offset, 8); + } + } + } else { + + /* No change no worries: just copy from the previous frame. */ + + for (i = 0; i < 8; i++) { + offset = ctx->y_stride * i; + + memcpy(dst + offset, src + offset, 8); + } + } + + src += 8; + dst += 8; + } + } + + /* + * Decode Cr and Cb planes. + */ + for (chrom_ch = 0; chrom_ch < 2; chrom_ch++) { + + base_offset = ctx->y_size + (ctx->crcb_size * chrom_ch); + + for (y = 0; y < ctx->num_vblocks_cbcr; y++) { + guint num_rows = 8; + + /* The last row of blocks in chrominance for 160x120 resolution + * is half the normal height and must be accounted for. */ + if (y + 1 == ctx->num_vblocks_cbcr && ctx->frame_height % 16 != 0) + num_rows = 4; + + offset = base_offset + (ctx->crcb_stride * 8 * y); + + src = ctx->prev_frame_buf + offset; + dst = ctx->cur_frame_buf + offset; + + for (x = 0; x < ctx->num_hblocks_cbcr; x++) { + + /* Check for a change condition in the current block. */ + + if (is_pframe) + bit = _read_bits(ctx, 1); + else + bit = 1; + + if (bit == 1) { + + /* Yes: decode it. */ + + if (_vlc_decode_block(ctx, dct_block, ctx->num_coeffs) == FALSE) { + + /* Corrupted frame: clear Cr and Cb planes and return. */ + p = ctx->cur_frame_buf + ctx->y_size; + memset(p, 128, ctx->crcb_size * 2); + + return FALSE; + } + + _idct_dequant_block(ctx, dct_block, 1); + + for (i = 0; i < num_rows; i++) { + p = dst + (ctx->crcb_stride * i); + + for (j = 0; j < 8; j++) + p[j] = dct_block[(i * 8) + j]; + } + + } else { + + /* No change no worries: just copy from the previous frame. */ + + for (i = 0; i < num_rows; i++) { + offset = ctx->crcb_stride * i; + + memcpy(dst + offset, src + offset, 8); + } + } + + src += 8; + dst += 8; + } + } + } + + /* + * Make a copy of the current frame and store in + * the circular pointer list of 16 entries. + */ + ctx->prev_frame_buf = ctx->buf_ptrs[ctx->ptr_index]; + memcpy(ctx->prev_frame_buf, ctx->cur_frame_buf, + ctx->y_size + (ctx->crcb_size * 2)); + + if (--ctx->ptr_index < 0) + ctx->ptr_index = 15; + + /* + * Perform deblocking on all planes. + */ + _deblock(ctx->cur_frame_buf, + ctx->y_stride, ctx->y_row_count); + + _deblock(ctx->cur_frame_buf + ctx->y_size, + ctx->crcb_stride, ctx->crcb_row_count); + + _deblock(ctx->cur_frame_buf + ctx->y_size + ctx->crcb_size, + ctx->crcb_stride, ctx->crcb_row_count); + + return TRUE; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/encode.c b/kopete/protocols/msn/webcam/libmimic/encode.c new file mode 100644 index 00000000..909ebd80 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/encode.c @@ -0,0 +1,419 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include "mimic-private.h" + +#define LUMINANCE_THRESHOLD 32.0f +#define CHROMINANCE_THRESHOLD 36.0f + +static void encode_main(MimCtx *ctx, guchar *out_buf, gboolean is_pframe); + +/** + * Encode a MIMIC-encoded frame from RGB data. + * + * @param ctx the mimic context + * @param input_buffer buffer containing pixeldata in RGB 24-bpp packed pixel top-down format + * @param output_buffer buffer that will receive the MIMIC-encoded frame + * (use #mimic_get_property to determine the required buffer size) + * @param output_length pointer to an integer that receives the length of the encoded data + * written to output_buffer + * @param make_keyframe whether the encoder should make this frame a keyframe + * @returns #TRUE on success + */ +gboolean mimic_encode_frame(MimCtx *ctx, + const guchar *input_buffer, + guchar *output_buffer, + gint *output_length, + gboolean make_keyframe) +{ + guchar *output_y, *output_cb, *output_cr; + + /* + * Some sanity checks. + */ + if (ctx == NULL || input_buffer == NULL || + output_buffer == NULL || output_length == NULL) + { + return FALSE; + } + + if (!ctx->encoder_initialized) + return FALSE; + + /* + * Initialize state. + */ + ctx->chunk_ptr = (guint32 *) (output_buffer + 20); + ctx->cur_chunk = 0; + ctx->cur_chunk_len = 0; + + if (ctx->frame_num == 0) + make_keyframe = TRUE; + + /* + * Write header. + */ + memset(output_buffer, 0, 20); + *((guint16 *) (output_buffer + 0)) = GUINT16_TO_LE(256); + *((guint16 *) (output_buffer + 2)) = GUINT16_TO_LE(ctx->quality); + *((guint16 *) (output_buffer + 4)) = GUINT16_TO_LE(ctx->frame_width); + *((guint16 *) (output_buffer + 6)) = GUINT16_TO_LE(ctx->frame_height); + *((guint32 *) (output_buffer + 12)) = GUINT32_TO_LE((make_keyframe == 0)); + *(output_buffer + 16) = ctx->num_coeffs; + *(output_buffer + 17) = 0; + + /* + * Perform RGB to YUV 420 conversion. + */ + output_y = ctx->cur_frame_buf; + output_cr = ctx->cur_frame_buf + ctx->y_size; + output_cb = ctx->cur_frame_buf + ctx->y_size + ctx->crcb_size; + + _rgb_to_yuv(input_buffer, + output_y, + output_cb, + output_cr, + ctx->frame_width, + ctx->frame_height); + + /* + * Encode frame. + */ + encode_main(ctx, output_buffer, (make_keyframe == FALSE)); + + /* + * Write out any pending bits to stream by zero-padding with 32 bits. + */ + _write_bits(ctx, 0, 32); + + /* + * Calculate bytes written. + */ + *output_length = (guchar *) ctx->chunk_ptr - output_buffer; + + /* + * Increment frame counter. + */ + ctx->frame_num++; + + return TRUE; +} + +static gdouble compare_blocks(const guchar *p1, + const guchar *p2, + gint stride, + gint row_count, + gboolean is_chrom); + +/* + * encode_main + * + * Main encoding loop. + */ +static void encode_main(MimCtx *ctx, guchar *out_buf, gboolean is_pframe) +{ + gint x, y, i, offset, chrom_ch; + gint dct_block[64]; + guchar *src, *dst, *p1, *p2; + gdouble match; + gboolean encoded; + + /* + * Round down small differences in luminance channel. + */ + if (is_pframe) { + + p1 = ctx->cur_frame_buf; + p2 = ctx->prev_frame_buf; + + for (i = 0; i < ctx->y_size; i++) { + + if (abs(p2[0] - p1[0]) < 7) + p1[0] = p2[0]; + + p1++; + p2++; + } + } + + /* + * Encode Y plane. + */ + for (y = 0; y < ctx->num_vblocks_y; y++) { + + for (x = 0; x < ctx->num_hblocks_y; x++) { + + /* Calculate final offset into buffer. */ + offset = (ctx->y_stride * 8 * y) + (x * 8); + + src = NULL; + encoded = FALSE; + + if (is_pframe) { + + /* Is the current block similar enough to what it was in the previous frame? */ + + match = compare_blocks(ctx->cur_frame_buf + offset, + ctx->prev_frame_buf + offset, + ctx->y_stride, 8, + FALSE); + + if (match > LUMINANCE_THRESHOLD) { + + /* Yes: write out '1' to indicate a no-change condition. */ + + _write_bits(ctx, 1, 1); + + src = ctx->prev_frame_buf + offset; + encoded = TRUE; + + } else { + + /* No: Is the current block similar enough to what it was in one + * of the (up to) 15 last frames preceding the previous? */ + + gint best_index = 0; + gdouble best_match = 0.0; + + gint num_backrefs = ctx->frame_num - 1; + if (num_backrefs > 15) + num_backrefs = 15; + + for (i = 1; i <= num_backrefs; i++) { + + match = compare_blocks(ctx->buf_ptrs[(ctx->ptr_index + i) % 16] + offset, + ctx->cur_frame_buf + offset, + ctx->y_stride, 8, + FALSE); + + if (match > LUMINANCE_THRESHOLD && match > best_match) { + best_index = i; + best_match = match; + } + + } + + if (best_index != 0) { + + /* Yes: write out '01' to indicate a "change but like previous"-condition, + * followed by 4 bits containing the back-reference. */ + _write_bits(ctx, 0, 1); + _write_bits(ctx, 1, 1); + _write_bits(ctx, best_index, 4); + + src = ctx->buf_ptrs[(ctx->ptr_index + best_index) % 16] + offset; + encoded = TRUE; + + } + } + } + + if (!encoded) { + + /* Keyframe or in any case no? ;-) Well, encode it then. */ + + if (is_pframe) { + _write_bits(ctx, 0, 1); + _write_bits(ctx, 0, 1); + } + + _fdct_quant_block(ctx, + dct_block, + ctx->cur_frame_buf + offset, + ctx->y_stride, + FALSE, + ctx->num_coeffs); + + _vlc_encode_block(ctx, + dct_block, + ctx->num_coeffs); + + } + + /* And if there was some kind of no-change condition, + * we want to copy the previous block. */ + if (src != NULL) { + + dst = ctx->cur_frame_buf + offset; + for (i = 0; i < 8; i++) { + + memcpy(dst, src, 8); + + src += ctx->y_stride; + dst += ctx->y_stride; + } + + } + + } + + } + + /* + * Encode Cr and Cb planes. + */ + for (chrom_ch = 0; chrom_ch < 2; chrom_ch++) { + + /* Calculate base offset into buffer. */ + gint base_offset = ctx->y_size + (ctx->crcb_size * chrom_ch); + + for (y = 0; y < ctx->num_vblocks_cbcr; y++) { + guchar tmp_block[64]; + guint num_rows = 8; + + /* The last row of blocks in chrominance for 160x120 resolution + * is half the normal height and must be accounted for. */ + if (y + 1 == ctx->num_vblocks_cbcr && ctx->frame_height % 16 != 0) + num_rows = 4; + + for (x = 0; x < ctx->num_hblocks_cbcr; x++) { + + /* Calculate final offset into buffer. */ + offset = base_offset + (ctx->crcb_stride * 8 * y) + (x * 8); + + src = NULL; + encoded = FALSE; + + if (is_pframe) { + + /* Is the current block similar enough to what it was in the previous frame? */ + + match = compare_blocks(ctx->prev_frame_buf + offset, + ctx->cur_frame_buf + offset, + ctx->crcb_stride, num_rows, + TRUE); + + if (match > CHROMINANCE_THRESHOLD) { + + /* Yes: write out '0' to indicate a no-change condition. */ + + _write_bits(ctx, 0, 1); + + encoded = TRUE; + + src = ctx->prev_frame_buf + offset; + dst = ctx->cur_frame_buf + offset; + for (i = 0; i < num_rows; i++) { + + memcpy(dst, src, 8); + + src += ctx->crcb_stride; + dst += ctx->crcb_stride; + } + } + + } + + if (!encoded) { + + /* Keyframe or just not similar enough? ;-) Well, encode it then. */ + + if (is_pframe) + _write_bits(ctx, 1, 1); + + /* Use a temporary array to handle cases where the + * current block is not of normal height (see above). */ + src = ctx->cur_frame_buf + offset; + dst = tmp_block; + for (i = 0; i < 8; i++) { + + memcpy(dst, src, 8); + + if (i < (num_rows - 1)) + src += ctx->crcb_stride; + dst += 8; + } + + _fdct_quant_block(ctx, + dct_block, + tmp_block, + 8, + TRUE, + ctx->num_coeffs); + + _vlc_encode_block(ctx, + dct_block, + ctx->num_coeffs); + + } + + } + + } + + } + + /* + * Make a copy of the current frame and store in + * the circular pointer list of 16 entries. + */ + ctx->prev_frame_buf = ctx->buf_ptrs[ctx->ptr_index]; + memcpy(ctx->prev_frame_buf, ctx->cur_frame_buf, + ctx->y_size + (ctx->crcb_size * 2)); + + if (--ctx->ptr_index < 0) + ctx->ptr_index = 15; +} + +/* + * compare_blocks + * + * Helper-function used to compare two blocks and + * determine how similar they are. + */ +static gdouble compare_blocks(const guchar *p1, + const guchar *p2, + gint stride, + gint row_count, + gboolean is_chrom) +{ + gint i, j, sum; + gdouble d; + + sum = 0; + + for (i = 0; i < row_count; i++) { + + for (j = 0; j < 8; j++) { + + gint d = p2[j] - p1[j]; + + sum += d * d; + } + + p1 += stride; + p2 += stride; + } + + if (is_chrom) { + if (row_count == 8) + d = sum * 0.015625; + else + d = sum * 0.03125; + } else { + d = sum / 64; + } + + if (d == 0.0f) + return 100.0f; + else + return (10.0f * log(65025.0f / d)) / G_LN10; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/fdct_quant.c b/kopete/protocols/msn/webcam/libmimic/fdct_quant.c new file mode 100644 index 00000000..7e8d0bdd --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/fdct_quant.c @@ -0,0 +1,181 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "mimic-private.h" + +extern guchar _col_zag[64]; + +void _fdct_quant_block(MimCtx *ctx, gint *block, const guchar *src, + gint stride, gboolean is_chrom, gint num_coeffs) +{ + gint sum1, sum2, sum3, sum4; + gint diff1, diff2, diff3, diff4; + gint ex1, ex2, ex3, ex4, ex5; + gint i, j; + const guchar *p1; + gint *iptr; + + /* + * Forward DCT, first pass (horizontal). + */ + p1 = src; + iptr = block; + + for (i = 0; i < 8; i++) { + sum1 = p1[0] + p1[7]; + sum2 = p1[1] + p1[6]; + sum3 = p1[2] + p1[5]; + sum4 = p1[3] + p1[4]; + + diff1 = p1[0] - p1[7]; + diff2 = p1[1] - p1[6]; + diff3 = p1[2] - p1[5]; + diff4 = p1[3] - p1[4]; + + ex1 = ((diff1 + diff4) * 851) - (diff1 * 282); + ex2 = ((diff2 + diff3) * 1004) - (diff2 * 804); + ex3 = ((diff2 + diff3) * 1004) - (diff3 * 1204); + ex4 = ((diff1 + diff4) * 851) - (diff4 * 1420); + + iptr[0] = sum1 + sum2 + sum3 + sum4; + iptr[2] = (((sum1 - sum4) * 1337) + ((sum2 - sum3) * 554)) >> 10; + iptr[4] = sum1 - sum2 - sum3 + sum4; + + iptr[1] = (ex1 + ex2 + ex3 + ex4) >> 10; + iptr[3] = ((ex4 - ex2) * 181) >> 17; + iptr[5] = ((ex1 - ex3) * 181) >> 17; + + p1 += stride; + iptr += 8; + } + + p1 = src; + iptr = block; + + /* + * Forward DCT, first pass (vertical). + * + * This is only known to be correct for i == 0, though it seems to be ... + */ + for (i = 0; i < 6; i++) { + sum1 = iptr[ 0 + i] + iptr[56 + i]; + sum2 = iptr[ 8 + i] + iptr[48 + i]; + sum3 = iptr[16 + i] + iptr[40 + i]; + sum4 = iptr[24 + i] + iptr[32 + i]; + + diff1 = iptr[ 0 + i] - iptr[56 + i]; + diff2 = iptr[ 8 + i] - iptr[48 + i]; + diff3 = iptr[16 + i] - iptr[40 + i]; + diff4 = iptr[24 + i] - iptr[32 + i]; + + ex1 = ((diff1 + diff4) * 851) - (diff1 * 282); + ex2 = ((diff2 + diff3) * 1004) - (diff2 * 804); + ex3 = ((diff2 + diff3) * 1004) - (diff3 * 1204); + ex4 = ((diff1 + diff4) * 851) - (diff4 * 1420); + + ex5 = (sum1 + sum2 - sum3 - sum4) * 554; + + for (j = 0; j < 7 - i; j++) { + switch (j) { + + case 0: + iptr[ 0 + i] = (16 + sum1 + sum2 + sum3 + sum4) >> 5; + break; + + case 1: + iptr[ 8 + i] = (16384 + ex1 + ex2 + ex3 + ex4) >> 15; + break; + + case 2: + iptr[16 + i] = (16384 + ((sum1 - sum4) * 783) + ex5) >> 15; + break; + + case 3: + iptr[24 + i] = (8192 + (((ex4 - ex2) >> 8) * 181)) >> 14; + break; + + case 4: + iptr[32 + i] = (16 + sum1 - sum2 - sum3 + sum4) >> 5; + break; + + case 5: + iptr[40 + i] = (8192 + (((ex1 - ex3) >> 8) * 181)) >> 14; + break; + + case 6: + iptr[48 + i] = (16384 - ((sum2 - sum3) * 1891) + ex5) >> 15; + break; + } + } + } + + /* + * Quantize. + */ + block[0] /= 2; + block[8] /= 4; + block[1] /= 4; + block[6] = 0; + + if (num_coeffs > 3) { + + gdouble s = (10000 - ctx->quality) * 10.0 * (gfloat) 9.9999997e-5; + + if (s > 10.0) + s = 10.0; + else if (is_chrom != 0 && s < 1.0) + s = 1.0; + else if (s < 2.0) + s = 2.0; + + s = 1.0 / s; + + for (i = 3; i < num_coeffs; i++) { + + gdouble coeff, r; + + coeff = block[_col_zag[i]] * s; + r = coeff - (gint) coeff; + + if (r >= 0.6) + block[_col_zag[i]] = (gint) (coeff + 1.0); + else if (r <= -0.6) + block[_col_zag[i]] = (gint) (coeff - 1.0); + else + block[_col_zag[i]] = (gint) coeff; + + if (block[_col_zag[i]] > 120) + block[_col_zag[i]] = 120; + else if (block[_col_zag[i]] < -120) + block[_col_zag[i]] = -120; + } + } + + if (block[8] > 120) + block[8] = 120; + else if (block[8] < -120) + block[8] = -120; + + if (block[1] > 120) + block[1] = 120; + else if (block[1] < -120) + block[1] = -120; + + for (i = num_coeffs; i < 64; i++) + block[_col_zag[i]] = 0; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/idct_dequant.c b/kopete/protocols/msn/webcam/libmimic/idct_dequant.c new file mode 100644 index 00000000..e5d64fb4 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/idct_dequant.c @@ -0,0 +1,134 @@ +/* Copyright (C) 2005 Ole Andr Vadla Ravns <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "mimic-private.h" + +void _idct_dequant_block(MimCtx *ctx, gint *block, gboolean is_chrom) +{ + gdouble f; + gint i, *p; + + /* + * De-quantize. + */ + f = (10000 - ctx->quality) * 10.0 * (gfloat) 9.9999997e-5; + + if (f > 10.0) + f = 10.0; + + if (!is_chrom) { + if (f < 2.0) + f = 2.0; + } else { + if (f < 1.0) + f = 1.0; + } + + block[0] <<= 1; + block[1] <<= 2; + block[8] <<= 2; + + for (i = 2; i < 64; i++) { + if (i == 8) + continue; + + block[i] *= f; + } + + /* + * Inverse DCT, first pass (horizontal). + */ + p = block; + + for (i = 0; i < 8; i++) { + gint v1, v2, v3, v4, v5, v6, v7, v8; + gint va, vb; + + va = (p[0] << 11) + (p[4] << 11); + vb = ((p[2] << 2) * 392) + (((p[2] << 2) + (p[6] << 2)) * 277); + v1 = va + vb + 512; + v2 = va - vb + 512; + + va = (p[0] << 11) - (p[4] << 11); + vb = (((p[2] << 2) + (p[6] << 2)) * 277) - ((p[6] << 2) * 946); + v3 = va + vb + 512; + v4 = va - vb + 512; + + va = (p[1] << 9) + (p[3] * 724) + (p[7] << 9); + vb = (p[1] << 9) + (p[5] * 724) - (p[7] << 9); + v5 = (((va + vb) * 213) - (vb * 71)) >> 6; + v6 = (((va + vb) * 213) - (va * 355)) >> 6; + + va = (p[1] << 9) - (p[3] * 724) + (p[7] << 9); + vb = (p[1] << 9) - (p[5] * 724) - (p[7] << 9); + v7 = (((va + vb) * 251) - (va * 201)) >> 6; + v8 = (((va + vb) * 251) - (vb * 301)) >> 6; + + p[0] = (v1 + v5) >> 10; + p[1] = (v3 + v7) >> 10; + p[2] = (v4 + v8) >> 10; + p[3] = (v2 + v6) >> 10; + p[4] = (v2 - v6) >> 10; + p[5] = (v4 - v8) >> 10; + p[6] = (v3 - v7) >> 10; + p[7] = (v1 - v5) >> 10; + + p += 8; + } + + /* + * Inverse dct, second pass (vertical). + */ + p = block; + + for (i = 0; i < 8; i++) { + gint v1, v2, v3, v4, v5, v6, v7, v8; + gint va, vb; + + va = (p[0] << 9) + (p[32] << 9); + vb = ((p[16] + p[48]) * 277) + (p[16] * 392); + v1 = va + vb + 1024; + v2 = va - vb + 1024; + + va = (p[0] << 9) - (p[32] << 9); + vb = ((p[16] + p[48]) * 277) - (p[48] * 946); + v3 = va + vb + 1024; + v4 = va - vb + 1024; + + va = ((p[8] << 7) + (p[24] * 181) + (p[56] << 7)) >> 6; + vb = ((p[8] << 7) + (p[40] * 181) - (p[56] << 7)) >> 6; + v5 = ((va + vb) * 213) - (vb * 71); + v6 = ((va + vb) * 213) - (va * 355); + + va = ((p[8] << 7) - (p[24] * 181) + (p[56] << 7)) >> 6; + vb = ((p[8] << 7) - (p[40] * 181) - (p[56] << 7)) >> 6; + v7 = ((va + vb) * 251) - (va * 201); + v8 = ((va + vb) * 251) - (vb * 301); + + p[0] = (v1 + v5) >> 11; + p[8] = (v3 + v7) >> 11; + p[16] = (v4 + v8) >> 11; + p[24] = (v2 + v6) >> 11; + p[32] = (v2 - v6) >> 11; + p[40] = (v4 - v8) >> 11; + p[48] = (v3 - v7) >> 11; + p[56] = (v1 - v5) >> 11; + + p++; + } +} + diff --git a/kopete/protocols/msn/webcam/libmimic/mimic-private.h b/kopete/protocols/msn/webcam/libmimic/mimic-private.h new file mode 100644 index 00000000..d33c50b7 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/mimic-private.h @@ -0,0 +1,117 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIMIC_PRIVATE_H +#define MIMIC_PRIVATE_H + +#include "mimic.h" + +#define ENCODER_BUFFER_SIZE 16384 +#define ENCODER_QUALITY_DEFAULT 0 +#define ENCODER_QUALITY_MIN 0 +#define ENCODER_QUALITY_MAX 10000 + +struct _MimCtx { + gboolean encoder_initialized; + gboolean decoder_initialized; + + gint frame_width; + gint frame_height; + gint quality; + gint num_coeffs; + + gint y_stride; + gint y_row_count; + gint y_size; + + gint crcb_stride; + gint crcb_row_count; + gint crcb_size; + + gint num_vblocks_y; + gint num_hblocks_y; + + gint num_vblocks_cbcr; + gint num_hblocks_cbcr; + + guchar *cur_frame_buf; + guchar *prev_frame_buf; + + gint8 vlcdec_lookup[2296]; + + gchar *data_buffer; + guint data_index; + + guint32 cur_chunk; + gint cur_chunk_len; + + guint32 *chunk_ptr; + gboolean read_odd; + + gint frame_num; + + gint ptr_index; + guchar *buf_ptrs[16]; +}; + +typedef struct { + guchar length1; + guint32 part1; + + guchar length2; + guint32 part2; +} VlcSymbol; + +typedef struct { + guint32 magic; + guchar pos_add; + guchar num_bits; +} VlcMagic; + +void _mimic_init(MimCtx *ctx, gint width, gint height); +guchar _clamp_value(gint value); + +guint32 _read_bits(MimCtx *ctx, gint num_bits); +void _write_bits(MimCtx *ctx, guint32 bits, gint length); + +void _vlc_encode_block(MimCtx *ctx, const gint *block, gint num_coeffs); +gboolean _vlc_decode_block(MimCtx *ctx, gint *block, gint num_coeffs); + +void _fdct_quant_block(MimCtx *ctx, gint *block, const guchar *src, + gint stride, gboolean is_chrom, gint num_coeffs); +void _idct_dequant_block(MimCtx *ctx, gint *block, gboolean is_chrom); + +VlcMagic *_find_magic(guint magic); +void _initialize_vlcdec_lookup(gint8 *lookup_tbl); + +void _rgb_to_yuv(const guchar *input_rgb, + guchar *output_y, + guchar *output_cb, + guchar *output_cr, + gint width, + gint height); +void _yuv_to_rgb(const guchar *input_y, + const guchar *input_cb, + const guchar *input_cr, + guchar *output_rgb, + guint width, + guint height); + +void _deblock(guchar *blocks, guint stride, guint row_count); + +#endif + diff --git a/kopete/protocols/msn/webcam/libmimic/mimic.c b/kopete/protocols/msn/webcam/libmimic/mimic.c new file mode 100644 index 00000000..95564755 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/mimic.c @@ -0,0 +1,334 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string.h> +#include "mimic-private.h" + +/** + * Creates a new instance and returns a pointer to the new context + * that can be used for either encoding or decoding by calling + * #mimic_encoder_init or #mimic_decoder_init. + * + * #mimic_close is called to free any resources associated with + * the context once done. + * + * @returns a new mimic context + */ +MimCtx *mimic_open() +{ + MimCtx *ctx; + + ctx = g_new0(MimCtx, 1); + + ctx->encoder_initialized = FALSE; + ctx->decoder_initialized = FALSE; + + return ctx; +} + +/** + * Frees any resources associated with the given context. + * + * @param ctx the mimic context to free + */ +void mimic_close(MimCtx *ctx) +{ + if (ctx->encoder_initialized || ctx->decoder_initialized) { + gint i; + + g_free(ctx->cur_frame_buf); + + for (i = 0; i < 16; i++) + g_free(ctx->buf_ptrs[i]); + } + + g_free(ctx); +} + +/* + * mimic_init + * + * Internal helper-function used to initialize + * a given context. + */ +static void mimic_init(MimCtx *ctx, gint width, gint height) +{ + gint bufsize, i; + + /* + * Dimensions-related. + */ + ctx->frame_width = width; + ctx->frame_height = height; + + ctx->y_stride = ctx->frame_width; + ctx->y_row_count = ctx->frame_height; + ctx->y_size = ctx->y_stride * ctx->y_row_count; + + ctx->crcb_stride = ctx->y_stride / 2; + ctx->crcb_row_count = ctx->y_row_count / 2; + ctx->crcb_size = ctx->crcb_stride * ctx->crcb_row_count; + + ctx->num_vblocks_y = ctx->frame_height / 8; + ctx->num_hblocks_y = ctx->frame_width / 8; + + ctx->num_vblocks_cbcr = ctx->frame_height / 16; + ctx->num_hblocks_cbcr = ctx->frame_width / 16; + + if (ctx->frame_height % 16 != 0) + ctx->num_vblocks_cbcr++; + + /* + * Initialize state. + */ + ctx->frame_num = 0; + ctx->ptr_index = 15; + ctx->num_coeffs = 28; + + /* + * Allocate memory for buffers. + */ + ctx->cur_frame_buf = g_new(guchar, (320 * 240 * 3) / 2); + + bufsize = ctx->y_size + (ctx->crcb_size * 2); + for (i = 0; i < 16; i++) + ctx->buf_ptrs[i] = g_new(guchar, bufsize); + + /* + * Initialize vlc lookup used by decoder. + */ + _initialize_vlcdec_lookup(ctx->vlcdec_lookup); +} + +/** + * Initialize the mimic encoder and prepare for encoding by + * initializing internal state and allocating resources as + * needed. + * + * After initializing use #mimic_get_property to determine + * the size of the output buffer needed for calls to + * #mimic_encode_frame. Use #mimic_set_property to set + * encoding quality. + * + * Note that once a given context has been initialized + * for either encoding or decoding it is not possible + * to initialize it again. + * + * @param ctx the mimic context to initialize + * @param resolution a #MimicResEnum used to specify the resolution + * @returns #TRUE on success + */ +gboolean mimic_encoder_init(MimCtx *ctx, const MimicResEnum resolution) +{ + gint width, height; + + /* Check if we've been initialized before. */ + if (ctx->encoder_initialized || ctx->decoder_initialized) + return FALSE; + + /* Check resolution. */ + if (resolution == MIMIC_RES_LOW) { + width = 160; + height = 120; + } else if (resolution == MIMIC_RES_HIGH) { + width = 320; + height = 240; + } else { + return FALSE; + } + + /* Initialize! */ + mimic_init(ctx, width, height); + + /* Set a default quality setting. */ + ctx->quality = ENCODER_QUALITY_DEFAULT; + + ctx->encoder_initialized = TRUE; + + return TRUE; +} + +/** + * Initialize the mimic decoder. The frame passed in frame_buffer + * is used to determine the resolution so that the internal state + * can be prepared and resources allocated accordingly. Note that + * the frame passed has to be a keyframe. + * + * After initializing use #mimic_get_property to determine required + * buffer-size, resolution, quality, etc. + * + * Note that once a given context has been initialized + * for either encoding or decoding it is not possible + * to initialize it again. + * + * @param ctx the mimic context to initialize + * @param frame_buffer buffer containing the first frame to decode + * @returns #TRUE on success + */ +gboolean mimic_decoder_init(MimCtx *ctx, const guchar *frame_buffer) +{ + gint width, height; + gboolean is_keyframe; + + /* Check if we've been initialized before and that + * frame_buffer is not NULL. */ + if (ctx->encoder_initialized || ctx->decoder_initialized || + frame_buffer == NULL) + { + return FALSE; + } + + /* Check resolution. */ + width = GUINT16_FROM_LE(*((guint16 *) (frame_buffer + 4))); + height = GUINT16_FROM_LE(*((guint16 *) (frame_buffer + 6))); + + if (!(width == 160 && height == 120) && !(width == 320 && height == 240)) + return FALSE; + + /* Check that we're initialized with a keyframe. */ + is_keyframe = (GUINT32_FROM_LE(*((guint32 *) (frame_buffer + 12))) == 0); + + if (!is_keyframe) + return FALSE; + + /* Get quality setting (in case we get queried for it before decoding). */ + ctx->quality = GUINT16_FROM_LE(*((guint16 *) (frame_buffer + 2))); + + /* Initialize! */ + mimic_init(ctx, width, height); + + ctx->decoder_initialized = TRUE; + + return TRUE; +} + +/** + * Get a property from a given mimic context. The context + * has to be initialized. + * + * Currently the following properties are defined: + * - "buffer_size" + * - Required output buffer size + * - "width" + * - Frame width + * - "height" + * - Frame height + * - "quality" + * - Encoder: Encoding quality used + * - Decoder: Decoding quality of the last known frame + * + * @param ctx the mimic context to retrieve the property from + * @param name of the property to retrieve the current value of + * @param data pointer to the data that will receive the retrieved value + * @returns #TRUE on success + */ +gboolean mimic_get_property(MimCtx *ctx, const gchar *name, gpointer data) +{ + /* Either the encoder or the decoder has to be initialized. */ + if (!ctx->encoder_initialized && !ctx->decoder_initialized) + return FALSE; + + if (ctx->encoder_initialized) { + + if (strcmp(name, "buffer_size") == 0) { + *((gint *) data) = ENCODER_BUFFER_SIZE; + + return TRUE; + } + + } else { /* decoder_initialized */ + + if (strcmp(name, "buffer_size") == 0) { + *((gint *) data) = ctx->frame_width * ctx->frame_height * 3; + + return TRUE; + } + } + + if (strcmp(name, "width") == 0) { + *((gint *) data) = ctx->frame_width; + + return TRUE; + } else if (strcmp(name, "height") == 0) { + *((gint *) data) = ctx->frame_height; + + return TRUE; + } else if (strcmp(name, "quality") == 0) { + *((gint *) data) = ctx->quality; + + return TRUE; + } + + return FALSE; +} + +/** + * Set a property in a given mimic context. The context + * has to be initialized. + * + * Currently the following properties are defined: + * - "quality" + * - Encoding quality used by encoder. + * + * @param ctx the mimic context to set a property in + * @param name of the property to set to a new value + * @param data pointer to the data that contains the new value + * @returns #TRUE on success + */ +gboolean mimic_set_property(MimCtx *ctx, const gchar *name, gpointer data) +{ + /* Either the encoder or the decoder has to be initialized. */ + if (!ctx->encoder_initialized && !ctx->decoder_initialized) + return FALSE; + + if (ctx->encoder_initialized) { + + if (strcmp(name, "quality") == 0) { + gint new_quality = *((gint *) data); + + if (new_quality < ENCODER_QUALITY_MIN || + new_quality > ENCODER_QUALITY_MAX) + { + return FALSE; + } + + ctx->quality = new_quality; + + return TRUE; + } + + } else { /* decoder_initialized */ } + + return FALSE; +} + +/* + * _clamp_value + * + * Internal helper-function used to clamp a given + * value to the range [ 0, 255 ]. + */ +guchar _clamp_value(gint value) +{ + if (value < 0) + return 0; + else if (value > 255) + return 255; + else + return value; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/mimic.h b/kopete/protocols/msn/webcam/libmimic/mimic.h new file mode 100644 index 00000000..491548f4 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/mimic.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#ifndef MIMIC_H +#define MIMIC_H + +#include <glib.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libmimic libmimic public API + * @brief The public API of the libmimic library + * + * libmimic provides the API required for encoding and decoding + * MIMIC v2.x-encoded content. + * + * @{ + */ + +/** + * The mimic encoding/decoding context returned by #mimic_open + * and used for all further API calls until #mimic_close. + */ +typedef struct _MimCtx MimCtx; + +typedef enum { + MIMIC_RES_LOW, /**< 160x120 resolution */ + MIMIC_RES_HIGH /**< 320x240 resolution */ +} MimicResEnum; + +MimCtx *mimic_open(); +void mimic_close(MimCtx *ctx); + +gboolean mimic_encoder_init(MimCtx *ctx, const MimicResEnum resolution); +gboolean mimic_decoder_init(MimCtx *ctx, const guchar *frame_buffer); + +gboolean mimic_get_property(MimCtx *ctx, const gchar *name, gpointer data); +gboolean mimic_set_property(MimCtx *ctx, const gchar *name, gpointer data); + +gboolean mimic_encode_frame(MimCtx *ctx, + const guchar *input_buffer, + guchar *output_buffer, + gint *output_length, + gboolean make_keyframe); +gboolean mimic_decode_frame(MimCtx *ctx, + const guchar *input_buffer, + guchar *output_buffer); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/kopete/protocols/msn/webcam/libmimic/query.c b/kopete/protocols/msn/webcam/libmimic/query.c new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/query.c @@ -0,0 +1 @@ + diff --git a/kopete/protocols/msn/webcam/libmimic/vlc_common.c b/kopete/protocols/msn/webcam/libmimic/vlc_common.c new file mode 100644 index 00000000..cbb0acc5 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/vlc_common.c @@ -0,0 +1,1364 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include "mimic-private.h" + +guchar _col_zag[64] = { + 0, 8, 1, 2, 9, 16, 24, 17, + 10, 3, 4, 11, 18, 25, 32, 40, + 33, 26, 19, 12, 5, 6, 13, 20, + 27, 34, 41, 48, 56, 49, 42, 35, + 28, 21, 14, 7, 15, 22, 29, 36, + 43, 50, 57, 58, 51, 44, 37, 30, + 23, 31, 38, 45, 52, 59, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +VlcSymbol _vlc_alphabet[16][128] = { + + /* + * base alphabet - no zeroes prefixed + */ + { + { 3, 0x1, 0, 0 }, { 4, 0x7, 0, 0 }, + { 4, 0x5, 0, 0 }, { 6, 0x27, 0, 0 }, + { 6, 0x25, 0, 0 }, { 6, 0x23, 0, 0 }, + { 6, 0x21, 0, 0 }, { 8, 0xcf, 0, 0 }, + { 8, 0xcd, 0, 0 }, { 8, 0xcb, 0, 0 }, + { 8, 0xc9, 0, 0 }, { 8, 0xc7, 0, 0 }, + { 8, 0xc5, 0, 0 }, { 8, 0xc3, 0, 0 }, + { 8, 0xc1, 0, 0 }, { 10, 0x35f, 0, 0 }, + { 10, 0x35d, 0, 0 }, { 10, 0x35b, 0, 0 }, + { 10, 0x359, 0, 0 }, { 10, 0x357, 0, 0 }, + { 10, 0x355, 0, 0 }, { 10, 0x353, 0, 0 }, + { 10, 0x351, 0, 0 }, { 10, 0x34f, 0, 0 }, + { 10, 0x34d, 0, 0 }, { 10, 0x34b, 0, 0 }, + { 10, 0x349, 0, 0 }, { 10, 0x347, 0, 0 }, + { 10, 0x345, 0, 0 }, { 10, 0x343, 0, 0 }, + { 10, 0x341, 0, 0 }, { 12, 0xeff, 0, 0 }, + { 12, 0xefd, 0, 0 }, { 12, 0xefb, 0, 0 }, + { 12, 0xef9, 0, 0 }, { 12, 0xef7, 0, 0 }, + { 12, 0xef5, 0, 0 }, { 12, 0xef3, 0, 0 }, + { 12, 0xef1, 0, 0 }, { 12, 0xeef, 0, 0 }, + { 12, 0xeed, 0, 0 }, { 12, 0xeeb, 0, 0 }, + { 12, 0xee9, 0, 0 }, { 12, 0xee7, 0, 0 }, + { 12, 0xee5, 0, 0 }, { 12, 0xee3, 0, 0 }, + { 12, 0xee1, 0, 0 }, { 12, 0xedf, 0, 0 }, + { 12, 0xedd, 0, 0 }, { 12, 0xedb, 0, 0 }, + { 12, 0xed9, 0, 0 }, { 12, 0xed7, 0, 0 }, + { 12, 0xed5, 0, 0 }, { 12, 0xed3, 0, 0 }, + { 12, 0xed1, 0, 0 }, { 12, 0xecf, 0, 0 }, + { 12, 0xecd, 0, 0 }, { 12, 0xecb, 0, 0 }, + { 12, 0xec9, 0, 0 }, { 12, 0xec7, 0, 0 }, + { 12, 0xec5, 0, 0 }, { 12, 0xec3, 0, 0 }, + { 12, 0xec1, 0, 0 }, { 17, 0x1fd7f, 0, 0 }, + { 17, 0x1fd7d, 0, 0 }, { 17, 0x1fd7b, 0, 0 }, + { 17, 0x1fd79, 0, 0 }, { 17, 0x1fd77, 0, 0 }, + { 17, 0x1fd75, 0, 0 }, { 17, 0x1fd73, 0, 0 }, + { 17, 0x1fd71, 0, 0 }, { 17, 0x1fd6f, 0, 0 }, + { 17, 0x1fd6d, 0, 0 }, { 17, 0x1fd6b, 0, 0 }, + { 17, 0x1fd69, 0, 0 }, { 17, 0x1fd67, 0, 0 }, + { 17, 0x1fd65, 0, 0 }, { 17, 0x1fd63, 0, 0 }, + { 17, 0x1fd61, 0, 0 }, { 17, 0x1fd5f, 0, 0 }, + { 17, 0x1fd5d, 0, 0 }, { 17, 0x1fd5b, 0, 0 }, + { 17, 0x1fd59, 0, 0 }, { 17, 0x1fd57, 0, 0 }, + { 17, 0x1fd55, 0, 0 }, { 17, 0x1fd53, 0, 0 }, + { 17, 0x1fd51, 0, 0 }, { 17, 0x1fd4f, 0, 0 }, + { 17, 0x1fd4d, 0, 0 }, { 17, 0x1fd4b, 0, 0 }, + { 17, 0x1fd49, 0, 0 }, { 17, 0x1fd47, 0, 0 }, + { 17, 0x1fd45, 0, 0 }, { 17, 0x1fd43, 0, 0 }, + { 17, 0x1fd41, 0, 0 }, { 17, 0x1fd3f, 0, 0 }, + { 17, 0x1fd3d, 0, 0 }, { 17, 0x1fd3b, 0, 0 }, + { 17, 0x1fd39, 0, 0 }, { 17, 0x1fd37, 0, 0 }, + { 17, 0x1fd35, 0, 0 }, { 17, 0x1fd33, 0, 0 }, + { 17, 0x1fd31, 0, 0 }, { 17, 0x1fd2f, 0, 0 }, + { 17, 0x1fd2d, 0, 0 }, { 17, 0x1fd2b, 0, 0 }, + { 17, 0x1fd29, 0, 0 }, { 17, 0x1fd27, 0, 0 }, + { 17, 0x1fd25, 0, 0 }, { 17, 0x1fd23, 0, 0 }, + { 17, 0x1fd21, 0, 0 }, { 17, 0x1fd1f, 0, 0 }, + { 17, 0x1fd1d, 0, 0 }, { 17, 0x1fd1b, 0, 0 }, + { 17, 0x1fd19, 0, 0 }, { 17, 0x1fd17, 0, 0 }, + { 17, 0x1fd15, 0, 0 }, { 17, 0x1fd13, 0, 0 }, + { 17, 0x1fd11, 0, 0 }, { 17, 0x1fd0f, 0, 0 }, + { 17, 0x1fd0d, 0, 0 }, { 17, 0x1fd0b, 0, 0 }, + { 17, 0x1fd09, 0, 0 }, { 17, 0x1fd07, 0, 0 }, + { 17, 0x1fd05, 0, 0 }, { 17, 0x1fd03, 0, 0 }, + { 17, 0x1fd01, 0, 0 }, { 17, 0x1fd01, 0, 0 } + }, + + /* + * prefixed with 1 zero + */ + { + { 5, 0x17, 0, 0 }, { 8, 0xe7, 0, 0 }, + { 8, 0xe5, 0, 0 }, { 9, 0x1d7, 0, 0 }, + { 9, 0x1d5, 0, 0 }, { 9, 0x1d3, 0, 0 }, + { 9, 0x1d1, 0, 0 }, { 12, 0xf8f, 0, 0 }, + { 12, 0xf8d, 0, 0 }, { 12, 0xf8b, 0, 0 }, + { 12, 0xf89, 0, 0 }, { 12, 0xf87, 0, 0 }, + { 12, 0xf85, 0, 0 }, { 12, 0xf83, 0, 0 }, + { 12, 0xf81, 0, 0 }, { 15, 0x7f1f, 0, 0 }, + { 15, 0x7f1d, 0, 0 }, { 15, 0x7f1b, 0, 0 }, + { 15, 0x7f19, 0, 0 }, { 15, 0x7f17, 0, 0 }, + { 15, 0x7f15, 0, 0 }, { 15, 0x7f13, 0, 0 }, + { 15, 0x7f11, 0, 0 }, { 15, 0x7f0f, 0, 0 }, + { 15, 0x7f0d, 0, 0 }, { 15, 0x7f0b, 0, 0 }, + { 15, 0x7f09, 0, 0 }, { 15, 0x7f07, 0, 0 }, + { 15, 0x7f05, 0, 0 }, { 15, 0x7f03, 0, 0 }, + { 15, 0x7f01, 0, 0 }, { 16, 0xfe7f, 0, 0 }, + { 16, 0xfe7d, 0, 0 }, { 16, 0xfe7b, 0, 0 }, + { 16, 0xfe79, 0, 0 }, { 16, 0xfe77, 0, 0 }, + { 16, 0xfe75, 0, 0 }, { 16, 0xfe73, 0, 0 }, + { 16, 0xfe71, 0, 0 }, { 16, 0xfe6f, 0, 0 }, + { 16, 0xfe6d, 0, 0 }, { 16, 0xfe6b, 0, 0 }, + { 16, 0xfe69, 0, 0 }, { 16, 0xfe67, 0, 0 }, + { 16, 0xfe65, 0, 0 }, { 16, 0xfe63, 0, 0 }, + { 16, 0xfe61, 0, 0 }, { 16, 0xfe5f, 0, 0 }, + { 16, 0xfe5d, 0, 0 }, { 16, 0xfe5b, 0, 0 }, + { 16, 0xfe59, 0, 0 }, { 16, 0xfe57, 0, 0 }, + { 16, 0xfe55, 0, 0 }, { 16, 0xfe53, 0, 0 }, + { 16, 0xfe51, 0, 0 }, { 16, 0xfe4f, 0, 0 }, + { 16, 0xfe4d, 0, 0 }, { 16, 0xfe4b, 0, 0 }, + { 16, 0xfe49, 0, 0 }, { 16, 0xfe47, 0, 0 }, + { 16, 0xfe45, 0, 0 }, { 16, 0xfe43, 0, 0 }, + { 16, 0xfe41, 0, 0 }, { 27, 0x7fffff9, 7, 0x7f }, + { 27, 0x7fffff9, 7, 0x7d }, { 27, 0x7fffff9, 7, 0x7b }, + { 27, 0x7fffff9, 7, 0x79 }, { 27, 0x7fffff9, 7, 0x77 }, + { 27, 0x7fffff9, 7, 0x75 }, { 27, 0x7fffff9, 7, 0x73 }, + { 27, 0x7fffff9, 7, 0x71 }, { 27, 0x7fffff9, 7, 0x6f }, + { 27, 0x7fffff9, 7, 0x6d }, { 27, 0x7fffff9, 7, 0x6b }, + { 27, 0x7fffff9, 7, 0x69 }, { 27, 0x7fffff9, 7, 0x67 }, + { 27, 0x7fffff9, 7, 0x65 }, { 27, 0x7fffff9, 7, 0x63 }, + { 27, 0x7fffff9, 7, 0x61 }, { 27, 0x7fffff9, 7, 0x5f }, + { 27, 0x7fffff9, 7, 0x5d }, { 27, 0x7fffff9, 7, 0x5b }, + { 27, 0x7fffff9, 7, 0x59 }, { 27, 0x7fffff9, 7, 0x57 }, + { 27, 0x7fffff9, 7, 0x55 }, { 27, 0x7fffff9, 7, 0x53 }, + { 27, 0x7fffff9, 7, 0x51 }, { 27, 0x7fffff9, 7, 0x4f }, + { 27, 0x7fffff9, 7, 0x4d }, { 27, 0x7fffff9, 7, 0x4b }, + { 27, 0x7fffff9, 7, 0x49 }, { 27, 0x7fffff9, 7, 0x47 }, + { 27, 0x7fffff9, 7, 0x45 }, { 27, 0x7fffff9, 7, 0x43 }, + { 27, 0x7fffff9, 7, 0x41 }, { 27, 0x7fffff9, 7, 0x3f }, + { 27, 0x7fffff9, 7, 0x3d }, { 27, 0x7fffff9, 7, 0x3b }, + { 27, 0x7fffff9, 7, 0x39 }, { 27, 0x7fffff9, 7, 0x37 }, + { 27, 0x7fffff9, 7, 0x35 }, { 27, 0x7fffff9, 7, 0x33 }, + { 27, 0x7fffff9, 7, 0x31 }, { 27, 0x7fffff9, 7, 0x2f }, + { 27, 0x7fffff9, 7, 0x2d }, { 27, 0x7fffff9, 7, 0x2b }, + { 27, 0x7fffff9, 7, 0x29 }, { 27, 0x7fffff9, 7, 0x27 }, + { 27, 0x7fffff9, 7, 0x25 }, { 27, 0x7fffff9, 7, 0x23 }, + { 27, 0x7fffff9, 7, 0x21 }, { 27, 0x7fffff9, 7, 0x1f }, + { 27, 0x7fffff9, 7, 0x1d }, { 27, 0x7fffff9, 7, 0x1b }, + { 27, 0x7fffff9, 7, 0x19 }, { 27, 0x7fffff9, 7, 0x17 }, + { 27, 0x7fffff9, 7, 0x15 }, { 27, 0x7fffff9, 7, 0x13 }, + { 27, 0x7fffff9, 7, 0x11 }, { 27, 0x7fffff9, 7, 0xf }, + { 27, 0x7fffff9, 7, 0xd }, { 27, 0x7fffff9, 7, 0xb }, + { 27, 0x7fffff9, 7, 0x9 }, { 27, 0x7fffff9, 7, 0x7 }, + { 27, 0x7fffff9, 7, 0x5 }, { 27, 0x7fffff9, 7, 0x3 }, + { 27, 0x7fffff9, 7, 0x1 }, { 27, 0x7fffff9, 7, 0x1 } + }, + + /* + * prefixed with 2 zeroes + */ + { + { 6, 0x37, 0, 0 }, { 9, 0x1ef, 0, 0 }, + { 9, 0x1ed, 0, 0 }, { 12, 0xfd7, 0, 0 }, + { 12, 0xfd5, 0, 0 }, { 12, 0xfd3, 0, 0 }, + { 12, 0xfd1, 0, 0 }, { 13, 0x1fbf, 0, 0 }, + { 13, 0x1fbd, 0, 0 }, { 13, 0x1fbb, 0, 0 }, + { 13, 0x1fb9, 0, 0 }, { 13, 0x1fb7, 0, 0 }, + { 13, 0x1fb5, 0, 0 }, { 13, 0x1fb3, 0, 0 }, + { 13, 0x1fb1, 0, 0 }, { 25, 0x1ffff7f, 0, 0 }, + { 25, 0x1ffff7d, 0, 0 }, { 25, 0x1ffff7b, 0, 0 }, + { 25, 0x1ffff79, 0, 0 }, { 25, 0x1ffff77, 0, 0 }, + { 25, 0x1ffff75, 0, 0 }, { 25, 0x1ffff73, 0, 0 }, + { 25, 0x1ffff71, 0, 0 }, { 25, 0x1ffff6f, 0, 0 }, + { 25, 0x1ffff6d, 0, 0 }, { 25, 0x1ffff6b, 0, 0 }, + { 25, 0x1ffff69, 0, 0 }, { 25, 0x1ffff67, 0, 0 }, + { 25, 0x1ffff65, 0, 0 }, { 25, 0x1ffff63, 0, 0 }, + { 25, 0x1ffff61, 0, 0 }, { 30, 0x3ffffe3f, 0, 0 }, + { 30, 0x3ffffe3d, 0, 0 }, { 30, 0x3ffffe3b, 0, 0 }, + { 30, 0x3ffffe39, 0, 0 }, { 30, 0x3ffffe37, 0, 0 }, + { 30, 0x3ffffe35, 0, 0 }, { 30, 0x3ffffe33, 0, 0 }, + { 30, 0x3ffffe31, 0, 0 }, { 30, 0x3ffffe2f, 0, 0 }, + { 30, 0x3ffffe2d, 0, 0 }, { 30, 0x3ffffe2b, 0, 0 }, + { 30, 0x3ffffe29, 0, 0 }, { 30, 0x3ffffe27, 0, 0 }, + { 30, 0x3ffffe25, 0, 0 }, { 30, 0x3ffffe23, 0, 0 }, + { 30, 0x3ffffe21, 0, 0 }, { 30, 0x3ffffe1f, 0, 0 }, + { 30, 0x3ffffe1d, 0, 0 }, { 30, 0x3ffffe1b, 0, 0 }, + { 30, 0x3ffffe19, 0, 0 }, { 30, 0x3ffffe17, 0, 0 }, + { 30, 0x3ffffe15, 0, 0 }, { 30, 0x3ffffe13, 0, 0 }, + { 30, 0x3ffffe11, 0, 0 }, { 30, 0x3ffffe0f, 0, 0 }, + { 30, 0x3ffffe0d, 0, 0 }, { 30, 0x3ffffe0b, 0, 0 }, + { 30, 0x3ffffe09, 0, 0 }, { 30, 0x3ffffe07, 0, 0 }, + { 30, 0x3ffffe05, 0, 0 }, { 30, 0x3ffffe03, 0, 0 }, + { 30, 0x3ffffe01, 0, 0 }, { 27, 0x7fffffa, 7, 0x7f }, + { 27, 0x7fffffa, 7, 0x7d }, { 27, 0x7fffffa, 7, 0x7b }, + { 27, 0x7fffffa, 7, 0x79 }, { 27, 0x7fffffa, 7, 0x77 }, + { 27, 0x7fffffa, 7, 0x75 }, { 27, 0x7fffffa, 7, 0x73 }, + { 27, 0x7fffffa, 7, 0x71 }, { 27, 0x7fffffa, 7, 0x6f }, + { 27, 0x7fffffa, 7, 0x6d }, { 27, 0x7fffffa, 7, 0x6b }, + { 27, 0x7fffffa, 7, 0x69 }, { 27, 0x7fffffa, 7, 0x67 }, + { 27, 0x7fffffa, 7, 0x65 }, { 27, 0x7fffffa, 7, 0x63 }, + { 27, 0x7fffffa, 7, 0x61 }, { 27, 0x7fffffa, 7, 0x5f }, + { 27, 0x7fffffa, 7, 0x5d }, { 27, 0x7fffffa, 7, 0x5b }, + { 27, 0x7fffffa, 7, 0x59 }, { 27, 0x7fffffa, 7, 0x57 }, + { 27, 0x7fffffa, 7, 0x55 }, { 27, 0x7fffffa, 7, 0x53 }, + { 27, 0x7fffffa, 7, 0x51 }, { 27, 0x7fffffa, 7, 0x4f }, + { 27, 0x7fffffa, 7, 0x4d }, { 27, 0x7fffffa, 7, 0x4b }, + { 27, 0x7fffffa, 7, 0x49 }, { 27, 0x7fffffa, 7, 0x47 }, + { 27, 0x7fffffa, 7, 0x45 }, { 27, 0x7fffffa, 7, 0x43 }, + { 27, 0x7fffffa, 7, 0x41 }, { 27, 0x7fffffa, 7, 0x3f }, + { 27, 0x7fffffa, 7, 0x3d }, { 27, 0x7fffffa, 7, 0x3b }, + { 27, 0x7fffffa, 7, 0x39 }, { 27, 0x7fffffa, 7, 0x37 }, + { 27, 0x7fffffa, 7, 0x35 }, { 27, 0x7fffffa, 7, 0x33 }, + { 27, 0x7fffffa, 7, 0x31 }, { 27, 0x7fffffa, 7, 0x2f }, + { 27, 0x7fffffa, 7, 0x2d }, { 27, 0x7fffffa, 7, 0x2b }, + { 27, 0x7fffffa, 7, 0x29 }, { 27, 0x7fffffa, 7, 0x27 }, + { 27, 0x7fffffa, 7, 0x25 }, { 27, 0x7fffffa, 7, 0x23 }, + { 27, 0x7fffffa, 7, 0x21 }, { 27, 0x7fffffa, 7, 0x1f }, + { 27, 0x7fffffa, 7, 0x1d }, { 27, 0x7fffffa, 7, 0x1b }, + { 27, 0x7fffffa, 7, 0x19 }, { 27, 0x7fffffa, 7, 0x17 }, + { 27, 0x7fffffa, 7, 0x15 }, { 27, 0x7fffffa, 7, 0x13 }, + { 27, 0x7fffffa, 7, 0x11 }, { 27, 0x7fffffa, 7, 0xf }, + { 27, 0x7fffffa, 7, 0xd }, { 27, 0x7fffffa, 7, 0xb }, + { 27, 0x7fffffa, 7, 0x9 }, { 27, 0x7fffffa, 7, 0x7 }, + { 27, 0x7fffffa, 7, 0x5 }, { 27, 0x7fffffa, 7, 0x3 }, + { 27, 0x7fffffa, 7, 0x1 }, { 27, 0x7fffffa, 7, 0x1 } + }, + + /* + * prefixed with 3 zeroes + */ + { + { 7, 0x71, 0, 0 }, { 10, 0x3ef, 0, 0 }, + { 10, 0x3ed, 0, 0 }, { 17, 0x1ffdf, 0, 0 }, + { 17, 0x1ffdd, 0, 0 }, { 17, 0x1ffdb, 0, 0 }, + { 17, 0x1ffd9, 0, 0 }, { 21, 0x1fffbf, 0, 0 }, + { 21, 0x1fffbd, 0, 0 }, { 21, 0x1fffbb, 0, 0 }, + { 21, 0x1fffb9, 0, 0 }, { 21, 0x1fffb7, 0, 0 }, + { 21, 0x1fffb5, 0, 0 }, { 21, 0x1fffb3, 0, 0 }, + { 21, 0x1fffb1, 0, 0 }, { 26, 0x3ffff1f, 0, 0 }, + { 26, 0x3ffff1d, 0, 0 }, { 26, 0x3ffff1b, 0, 0 }, + { 26, 0x3ffff19, 0, 0 }, { 26, 0x3ffff17, 0, 0 }, + { 26, 0x3ffff15, 0, 0 }, { 26, 0x3ffff13, 0, 0 }, + { 26, 0x3ffff11, 0, 0 }, { 26, 0x3ffff0f, 0, 0 }, + { 26, 0x3ffff0d, 0, 0 }, { 26, 0x3ffff0b, 0, 0 }, + { 26, 0x3ffff09, 0, 0 }, { 26, 0x3ffff07, 0, 0 }, + { 26, 0x3ffff05, 0, 0 }, { 26, 0x3ffff03, 0, 0 }, + { 26, 0x3ffff01, 0, 0 }, { 30, 0x3ffffe7f, 0, 0 }, + { 30, 0x3ffffe7d, 0, 0 }, { 30, 0x3ffffe7b, 0, 0 }, + { 30, 0x3ffffe79, 0, 0 }, { 30, 0x3ffffe77, 0, 0 }, + { 30, 0x3ffffe75, 0, 0 }, { 30, 0x3ffffe73, 0, 0 }, + { 30, 0x3ffffe71, 0, 0 }, { 30, 0x3ffffe6f, 0, 0 }, + { 30, 0x3ffffe6d, 0, 0 }, { 30, 0x3ffffe6b, 0, 0 }, + { 30, 0x3ffffe69, 0, 0 }, { 30, 0x3ffffe67, 0, 0 }, + { 30, 0x3ffffe65, 0, 0 }, { 30, 0x3ffffe63, 0, 0 }, + { 30, 0x3ffffe61, 0, 0 }, { 30, 0x3ffffe5f, 0, 0 }, + { 30, 0x3ffffe5d, 0, 0 }, { 30, 0x3ffffe5b, 0, 0 }, + { 30, 0x3ffffe59, 0, 0 }, { 30, 0x3ffffe57, 0, 0 }, + { 30, 0x3ffffe55, 0, 0 }, { 30, 0x3ffffe53, 0, 0 }, + { 30, 0x3ffffe51, 0, 0 }, { 30, 0x3ffffe4f, 0, 0 }, + { 30, 0x3ffffe4d, 0, 0 }, { 30, 0x3ffffe4b, 0, 0 }, + { 30, 0x3ffffe49, 0, 0 }, { 30, 0x3ffffe47, 0, 0 }, + { 30, 0x3ffffe45, 0, 0 }, { 30, 0x3ffffe43, 0, 0 }, + { 30, 0x3ffffe41, 0, 0 }, { 27, 0x7fffffb, 7, 0x7f }, + { 27, 0x7fffffb, 7, 0x7d }, { 27, 0x7fffffb, 7, 0x7b }, + { 27, 0x7fffffb, 7, 0x79 }, { 27, 0x7fffffb, 7, 0x77 }, + { 27, 0x7fffffb, 7, 0x75 }, { 27, 0x7fffffb, 7, 0x73 }, + { 27, 0x7fffffb, 7, 0x71 }, { 27, 0x7fffffb, 7, 0x6f }, + { 27, 0x7fffffb, 7, 0x6d }, { 27, 0x7fffffb, 7, 0x6b }, + { 27, 0x7fffffb, 7, 0x69 }, { 27, 0x7fffffb, 7, 0x67 }, + { 27, 0x7fffffb, 7, 0x65 }, { 27, 0x7fffffb, 7, 0x63 }, + { 27, 0x7fffffb, 7, 0x61 }, { 27, 0x7fffffb, 7, 0x5f }, + { 27, 0x7fffffb, 7, 0x5d }, { 27, 0x7fffffb, 7, 0x5b }, + { 27, 0x7fffffb, 7, 0x59 }, { 27, 0x7fffffb, 7, 0x57 }, + { 27, 0x7fffffb, 7, 0x55 }, { 27, 0x7fffffb, 7, 0x53 }, + { 27, 0x7fffffb, 7, 0x51 }, { 27, 0x7fffffb, 7, 0x4f }, + { 27, 0x7fffffb, 7, 0x4d }, { 27, 0x7fffffb, 7, 0x4b }, + { 27, 0x7fffffb, 7, 0x49 }, { 27, 0x7fffffb, 7, 0x47 }, + { 27, 0x7fffffb, 7, 0x45 }, { 27, 0x7fffffb, 7, 0x43 }, + { 27, 0x7fffffb, 7, 0x41 }, { 27, 0x7fffffb, 7, 0x3f }, + { 27, 0x7fffffb, 7, 0x3d }, { 27, 0x7fffffb, 7, 0x3b }, + { 27, 0x7fffffb, 7, 0x39 }, { 27, 0x7fffffb, 7, 0x37 }, + { 27, 0x7fffffb, 7, 0x35 }, { 27, 0x7fffffb, 7, 0x33 }, + { 27, 0x7fffffb, 7, 0x31 }, { 27, 0x7fffffb, 7, 0x2f }, + { 27, 0x7fffffb, 7, 0x2d }, { 27, 0x7fffffb, 7, 0x2b }, + { 27, 0x7fffffb, 7, 0x29 }, { 27, 0x7fffffb, 7, 0x27 }, + { 27, 0x7fffffb, 7, 0x25 }, { 27, 0x7fffffb, 7, 0x23 }, + { 27, 0x7fffffb, 7, 0x21 }, { 27, 0x7fffffb, 7, 0x1f }, + { 27, 0x7fffffb, 7, 0x1d }, { 27, 0x7fffffb, 7, 0x1b }, + { 27, 0x7fffffb, 7, 0x19 }, { 27, 0x7fffffb, 7, 0x17 }, + { 27, 0x7fffffb, 7, 0x15 }, { 27, 0x7fffffb, 7, 0x13 }, + { 27, 0x7fffffb, 7, 0x11 }, { 27, 0x7fffffb, 7, 0xf }, + { 27, 0x7fffffb, 7, 0xd }, { 27, 0x7fffffb, 7, 0xb }, + { 27, 0x7fffffb, 7, 0x9 }, { 27, 0x7fffffb, 7, 0x7 }, + { 27, 0x7fffffb, 7, 0x5 }, { 27, 0x7fffffb, 7, 0x3 }, + { 27, 0x7fffffb, 7, 0x1 }, { 27, 0x7fffffb, 7, 0x1 } + }, + + /* + * prefixed with 4 zeroes + */ + { + { 8, 0xf1, 0, 0 }, { 11, 0x7e3, 0, 0 }, + { 11, 0x7e1, 0, 0 }, { 18, 0x3ffc7, 0, 0 }, + { 18, 0x3ffc5, 0, 0 }, { 18, 0x3ffc3, 0, 0 }, + { 18, 0x3ffc1, 0, 0 }, { 22, 0x3fff8f, 0, 0 }, + { 22, 0x3fff8d, 0, 0 }, { 22, 0x3fff8b, 0, 0 }, + { 22, 0x3fff89, 0, 0 }, { 22, 0x3fff87, 0, 0 }, + { 22, 0x3fff85, 0, 0 }, { 22, 0x3fff83, 0, 0 }, + { 22, 0x3fff81, 0, 0 }, { 26, 0x3ffff3f, 0, 0 }, + { 26, 0x3ffff3d, 0, 0 }, { 26, 0x3ffff3b, 0, 0 }, + { 26, 0x3ffff39, 0, 0 }, { 26, 0x3ffff37, 0, 0 }, + { 26, 0x3ffff35, 0, 0 }, { 26, 0x3ffff33, 0, 0 }, + { 26, 0x3ffff31, 0, 0 }, { 26, 0x3ffff2f, 0, 0 }, + { 26, 0x3ffff2d, 0, 0 }, { 26, 0x3ffff2b, 0, 0 }, + { 26, 0x3ffff29, 0, 0 }, { 26, 0x3ffff27, 0, 0 }, + { 26, 0x3ffff25, 0, 0 }, { 26, 0x3ffff23, 0, 0 }, + { 26, 0x3ffff21, 0, 0 }, { 30, 0x3ffffebf, 0, 0 }, + { 30, 0x3ffffebd, 0, 0 }, { 30, 0x3ffffebb, 0, 0 }, + { 30, 0x3ffffeb9, 0, 0 }, { 30, 0x3ffffeb7, 0, 0 }, + { 30, 0x3ffffeb5, 0, 0 }, { 30, 0x3ffffeb3, 0, 0 }, + { 30, 0x3ffffeb1, 0, 0 }, { 30, 0x3ffffeaf, 0, 0 }, + { 30, 0x3ffffead, 0, 0 }, { 30, 0x3ffffeab, 0, 0 }, + { 30, 0x3ffffea9, 0, 0 }, { 30, 0x3ffffea7, 0, 0 }, + { 30, 0x3ffffea5, 0, 0 }, { 30, 0x3ffffea3, 0, 0 }, + { 30, 0x3ffffea1, 0, 0 }, { 30, 0x3ffffe9f, 0, 0 }, + { 30, 0x3ffffe9d, 0, 0 }, { 30, 0x3ffffe9b, 0, 0 }, + { 30, 0x3ffffe99, 0, 0 }, { 30, 0x3ffffe97, 0, 0 }, + { 30, 0x3ffffe95, 0, 0 }, { 30, 0x3ffffe93, 0, 0 }, + { 30, 0x3ffffe91, 0, 0 }, { 30, 0x3ffffe8f, 0, 0 }, + { 30, 0x3ffffe8d, 0, 0 }, { 30, 0x3ffffe8b, 0, 0 }, + { 30, 0x3ffffe89, 0, 0 }, { 30, 0x3ffffe87, 0, 0 }, + { 30, 0x3ffffe85, 0, 0 }, { 30, 0x3ffffe83, 0, 0 }, + { 30, 0x3ffffe81, 0, 0 }, { 28, 0xffffff8, 7, 0x7f }, + { 28, 0xffffff8, 7, 0x7d }, { 28, 0xffffff8, 7, 0x7b }, + { 28, 0xffffff8, 7, 0x79 }, { 28, 0xffffff8, 7, 0x77 }, + { 28, 0xffffff8, 7, 0x75 }, { 28, 0xffffff8, 7, 0x73 }, + { 28, 0xffffff8, 7, 0x71 }, { 28, 0xffffff8, 7, 0x6f }, + { 28, 0xffffff8, 7, 0x6d }, { 28, 0xffffff8, 7, 0x6b }, + { 28, 0xffffff8, 7, 0x69 }, { 28, 0xffffff8, 7, 0x67 }, + { 28, 0xffffff8, 7, 0x65 }, { 28, 0xffffff8, 7, 0x63 }, + { 28, 0xffffff8, 7, 0x61 }, { 28, 0xffffff8, 7, 0x5f }, + { 28, 0xffffff8, 7, 0x5d }, { 28, 0xffffff8, 7, 0x5b }, + { 28, 0xffffff8, 7, 0x59 }, { 28, 0xffffff8, 7, 0x57 }, + { 28, 0xffffff8, 7, 0x55 }, { 28, 0xffffff8, 7, 0x53 }, + { 28, 0xffffff8, 7, 0x51 }, { 28, 0xffffff8, 7, 0x4f }, + { 28, 0xffffff8, 7, 0x4d }, { 28, 0xffffff8, 7, 0x4b }, + { 28, 0xffffff8, 7, 0x49 }, { 28, 0xffffff8, 7, 0x47 }, + { 28, 0xffffff8, 7, 0x45 }, { 28, 0xffffff8, 7, 0x43 }, + { 28, 0xffffff8, 7, 0x41 }, { 28, 0xffffff8, 7, 0x3f }, + { 28, 0xffffff8, 7, 0x3d }, { 28, 0xffffff8, 7, 0x3b }, + { 28, 0xffffff8, 7, 0x39 }, { 28, 0xffffff8, 7, 0x37 }, + { 28, 0xffffff8, 7, 0x35 }, { 28, 0xffffff8, 7, 0x33 }, + { 28, 0xffffff8, 7, 0x31 }, { 28, 0xffffff8, 7, 0x2f }, + { 28, 0xffffff8, 7, 0x2d }, { 28, 0xffffff8, 7, 0x2b }, + { 28, 0xffffff8, 7, 0x29 }, { 28, 0xffffff8, 7, 0x27 }, + { 28, 0xffffff8, 7, 0x25 }, { 28, 0xffffff8, 7, 0x23 }, + { 28, 0xffffff8, 7, 0x21 }, { 28, 0xffffff8, 7, 0x1f }, + { 28, 0xffffff8, 7, 0x1d }, { 28, 0xffffff8, 7, 0x1b }, + { 28, 0xffffff8, 7, 0x19 }, { 28, 0xffffff8, 7, 0x17 }, + { 28, 0xffffff8, 7, 0x15 }, { 28, 0xffffff8, 7, 0x13 }, + { 28, 0xffffff8, 7, 0x11 }, { 28, 0xffffff8, 7, 0xf }, + { 28, 0xffffff8, 7, 0xd }, { 28, 0xffffff8, 7, 0xb }, + { 28, 0xffffff8, 7, 0x9 }, { 28, 0xffffff8, 7, 0x7 }, + { 28, 0xffffff8, 7, 0x5 }, { 28, 0xffffff8, 7, 0x3 }, + { 28, 0xffffff8, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 5 zeroes + */ + { + { 8, 0xf3, 0, 0 }, { 11, 0x7e7, 0, 0 }, + { 11, 0x7e5, 0, 0 }, { 18, 0x3ffcf, 0, 0 }, + { 18, 0x3ffcd, 0, 0 }, { 18, 0x3ffcb, 0, 0 }, + { 18, 0x3ffc9, 0, 0 }, { 22, 0x3fff9f, 0, 0 }, + { 22, 0x3fff9d, 0, 0 }, { 22, 0x3fff9b, 0, 0 }, + { 22, 0x3fff99, 0, 0 }, { 22, 0x3fff97, 0, 0 }, + { 22, 0x3fff95, 0, 0 }, { 22, 0x3fff93, 0, 0 }, + { 22, 0x3fff91, 0, 0 }, { 26, 0x3ffff5f, 0, 0 }, + { 26, 0x3ffff5d, 0, 0 }, { 26, 0x3ffff5b, 0, 0 }, + { 26, 0x3ffff59, 0, 0 }, { 26, 0x3ffff57, 0, 0 }, + { 26, 0x3ffff55, 0, 0 }, { 26, 0x3ffff53, 0, 0 }, + { 26, 0x3ffff51, 0, 0 }, { 26, 0x3ffff4f, 0, 0 }, + { 26, 0x3ffff4d, 0, 0 }, { 26, 0x3ffff4b, 0, 0 }, + { 26, 0x3ffff49, 0, 0 }, { 26, 0x3ffff47, 0, 0 }, + { 26, 0x3ffff45, 0, 0 }, { 26, 0x3ffff43, 0, 0 }, + { 26, 0x3ffff41, 0, 0 }, { 30, 0x3ffffeff, 0, 0 }, + { 30, 0x3ffffefd, 0, 0 }, { 30, 0x3ffffefb, 0, 0 }, + { 30, 0x3ffffef9, 0, 0 }, { 30, 0x3ffffef7, 0, 0 }, + { 30, 0x3ffffef5, 0, 0 }, { 30, 0x3ffffef3, 0, 0 }, + { 30, 0x3ffffef1, 0, 0 }, { 30, 0x3ffffeef, 0, 0 }, + { 30, 0x3ffffeed, 0, 0 }, { 30, 0x3ffffeeb, 0, 0 }, + { 30, 0x3ffffee9, 0, 0 }, { 30, 0x3ffffee7, 0, 0 }, + { 30, 0x3ffffee5, 0, 0 }, { 30, 0x3ffffee3, 0, 0 }, + { 30, 0x3ffffee1, 0, 0 }, { 30, 0x3ffffedf, 0, 0 }, + { 30, 0x3ffffedd, 0, 0 }, { 30, 0x3ffffedb, 0, 0 }, + { 30, 0x3ffffed9, 0, 0 }, { 30, 0x3ffffed7, 0, 0 }, + { 30, 0x3ffffed5, 0, 0 }, { 30, 0x3ffffed3, 0, 0 }, + { 30, 0x3ffffed1, 0, 0 }, { 30, 0x3ffffecf, 0, 0 }, + { 30, 0x3ffffecd, 0, 0 }, { 30, 0x3ffffecb, 0, 0 }, + { 30, 0x3ffffec9, 0, 0 }, { 30, 0x3ffffec7, 0, 0 }, + { 30, 0x3ffffec5, 0, 0 }, { 30, 0x3ffffec3, 0, 0 }, + { 30, 0x3ffffec1, 0, 0 }, { 28, 0xffffff9, 7, 0x7f }, + { 28, 0xffffff9, 7, 0x7d }, { 28, 0xffffff9, 7, 0x7b }, + { 28, 0xffffff9, 7, 0x79 }, { 28, 0xffffff9, 7, 0x77 }, + { 28, 0xffffff9, 7, 0x75 }, { 28, 0xffffff9, 7, 0x73 }, + { 28, 0xffffff9, 7, 0x71 }, { 28, 0xffffff9, 7, 0x6f }, + { 28, 0xffffff9, 7, 0x6d }, { 28, 0xffffff9, 7, 0x6b }, + { 28, 0xffffff9, 7, 0x69 }, { 28, 0xffffff9, 7, 0x67 }, + { 28, 0xffffff9, 7, 0x65 }, { 28, 0xffffff9, 7, 0x63 }, + { 28, 0xffffff9, 7, 0x61 }, { 28, 0xffffff9, 7, 0x5f }, + { 28, 0xffffff9, 7, 0x5d }, { 28, 0xffffff9, 7, 0x5b }, + { 28, 0xffffff9, 7, 0x59 }, { 28, 0xffffff9, 7, 0x57 }, + { 28, 0xffffff9, 7, 0x55 }, { 28, 0xffffff9, 7, 0x53 }, + { 28, 0xffffff9, 7, 0x51 }, { 28, 0xffffff9, 7, 0x4f }, + { 28, 0xffffff9, 7, 0x4d }, { 28, 0xffffff9, 7, 0x4b }, + { 28, 0xffffff9, 7, 0x49 }, { 28, 0xffffff9, 7, 0x47 }, + { 28, 0xffffff9, 7, 0x45 }, { 28, 0xffffff9, 7, 0x43 }, + { 28, 0xffffff9, 7, 0x41 }, { 28, 0xffffff9, 7, 0x3f }, + { 28, 0xffffff9, 7, 0x3d }, { 28, 0xffffff9, 7, 0x3b }, + { 28, 0xffffff9, 7, 0x39 }, { 28, 0xffffff9, 7, 0x37 }, + { 28, 0xffffff9, 7, 0x35 }, { 28, 0xffffff9, 7, 0x33 }, + { 28, 0xffffff9, 7, 0x31 }, { 28, 0xffffff9, 7, 0x2f }, + { 28, 0xffffff9, 7, 0x2d }, { 28, 0xffffff9, 7, 0x2b }, + { 28, 0xffffff9, 7, 0x29 }, { 28, 0xffffff9, 7, 0x27 }, + { 28, 0xffffff9, 7, 0x25 }, { 28, 0xffffff9, 7, 0x23 }, + { 28, 0xffffff9, 7, 0x21 }, { 28, 0xffffff9, 7, 0x1f }, + { 28, 0xffffff9, 7, 0x1d }, { 28, 0xffffff9, 7, 0x1b }, + { 28, 0xffffff9, 7, 0x19 }, { 28, 0xffffff9, 7, 0x17 }, + { 28, 0xffffff9, 7, 0x15 }, { 28, 0xffffff9, 7, 0x13 }, + { 28, 0xffffff9, 7, 0x11 }, { 28, 0xffffff9, 7, 0xf }, + { 28, 0xffffff9, 7, 0xd }, { 28, 0xffffff9, 7, 0xb }, + { 28, 0xffffff9, 7, 0x9 }, { 28, 0xffffff9, 7, 0x7 }, + { 28, 0xffffff9, 7, 0x5 }, { 28, 0xffffff9, 7, 0x3 }, + { 28, 0xffffff9, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 6 zeroes + */ + { + { 8, 0xf5, 0, 0 }, { 14, 0x3feb, 0, 0 }, + { 14, 0x3fe9, 0, 0 }, { 18, 0x3ffd7, 0, 0 }, + { 18, 0x3ffd5, 0, 0 }, { 18, 0x3ffd3, 0, 0 }, + { 18, 0x3ffd1, 0, 0 }, { 22, 0x3fffaf, 0, 0 }, + { 22, 0x3fffad, 0, 0 }, { 22, 0x3fffab, 0, 0 }, + { 22, 0x3fffa9, 0, 0 }, { 22, 0x3fffa7, 0, 0 }, + { 22, 0x3fffa5, 0, 0 }, { 22, 0x3fffa3, 0, 0 }, + { 22, 0x3fffa1, 0, 0 }, { 26, 0x3ffff7f, 0, 0 }, + { 26, 0x3ffff7d, 0, 0 }, { 26, 0x3ffff7b, 0, 0 }, + { 26, 0x3ffff79, 0, 0 }, { 26, 0x3ffff77, 0, 0 }, + { 26, 0x3ffff75, 0, 0 }, { 26, 0x3ffff73, 0, 0 }, + { 26, 0x3ffff71, 0, 0 }, { 26, 0x3ffff6f, 0, 0 }, + { 26, 0x3ffff6d, 0, 0 }, { 26, 0x3ffff6b, 0, 0 }, + { 26, 0x3ffff69, 0, 0 }, { 26, 0x3ffff67, 0, 0 }, + { 26, 0x3ffff65, 0, 0 }, { 26, 0x3ffff63, 0, 0 }, + { 26, 0x3ffff61, 0, 0 }, { 31, 0x7ffffe3f, 0, 0 }, + { 31, 0x7ffffe3d, 0, 0 }, { 31, 0x7ffffe3b, 0, 0 }, + { 31, 0x7ffffe39, 0, 0 }, { 31, 0x7ffffe37, 0, 0 }, + { 31, 0x7ffffe35, 0, 0 }, { 31, 0x7ffffe33, 0, 0 }, + { 31, 0x7ffffe31, 0, 0 }, { 31, 0x7ffffe2f, 0, 0 }, + { 31, 0x7ffffe2d, 0, 0 }, { 31, 0x7ffffe2b, 0, 0 }, + { 31, 0x7ffffe29, 0, 0 }, { 31, 0x7ffffe27, 0, 0 }, + { 31, 0x7ffffe25, 0, 0 }, { 31, 0x7ffffe23, 0, 0 }, + { 31, 0x7ffffe21, 0, 0 }, { 31, 0x7ffffe1f, 0, 0 }, + { 31, 0x7ffffe1d, 0, 0 }, { 31, 0x7ffffe1b, 0, 0 }, + { 31, 0x7ffffe19, 0, 0 }, { 31, 0x7ffffe17, 0, 0 }, + { 31, 0x7ffffe15, 0, 0 }, { 31, 0x7ffffe13, 0, 0 }, + { 31, 0x7ffffe11, 0, 0 }, { 31, 0x7ffffe0f, 0, 0 }, + { 31, 0x7ffffe0d, 0, 0 }, { 31, 0x7ffffe0b, 0, 0 }, + { 31, 0x7ffffe09, 0, 0 }, { 31, 0x7ffffe07, 0, 0 }, + { 31, 0x7ffffe05, 0, 0 }, { 31, 0x7ffffe03, 0, 0 }, + { 31, 0x7ffffe01, 0, 0 }, { 28, 0xffffffa, 7, 0x7f }, + { 28, 0xffffffa, 7, 0x7d }, { 28, 0xffffffa, 7, 0x7b }, + { 28, 0xffffffa, 7, 0x79 }, { 28, 0xffffffa, 7, 0x77 }, + { 28, 0xffffffa, 7, 0x75 }, { 28, 0xffffffa, 7, 0x73 }, + { 28, 0xffffffa, 7, 0x71 }, { 28, 0xffffffa, 7, 0x6f }, + { 28, 0xffffffa, 7, 0x6d }, { 28, 0xffffffa, 7, 0x6b }, + { 28, 0xffffffa, 7, 0x69 }, { 28, 0xffffffa, 7, 0x67 }, + { 28, 0xffffffa, 7, 0x65 }, { 28, 0xffffffa, 7, 0x63 }, + { 28, 0xffffffa, 7, 0x61 }, { 28, 0xffffffa, 7, 0x5f }, + { 28, 0xffffffa, 7, 0x5d }, { 28, 0xffffffa, 7, 0x5b }, + { 28, 0xffffffa, 7, 0x59 }, { 28, 0xffffffa, 7, 0x57 }, + { 28, 0xffffffa, 7, 0x55 }, { 28, 0xffffffa, 7, 0x53 }, + { 28, 0xffffffa, 7, 0x51 }, { 28, 0xffffffa, 7, 0x4f }, + { 28, 0xffffffa, 7, 0x4d }, { 28, 0xffffffa, 7, 0x4b }, + { 28, 0xffffffa, 7, 0x49 }, { 28, 0xffffffa, 7, 0x47 }, + { 28, 0xffffffa, 7, 0x45 }, { 28, 0xffffffa, 7, 0x43 }, + { 28, 0xffffffa, 7, 0x41 }, { 28, 0xffffffa, 7, 0x3f }, + { 28, 0xffffffa, 7, 0x3d }, { 28, 0xffffffa, 7, 0x3b }, + { 28, 0xffffffa, 7, 0x39 }, { 28, 0xffffffa, 7, 0x37 }, + { 28, 0xffffffa, 7, 0x35 }, { 28, 0xffffffa, 7, 0x33 }, + { 28, 0xffffffa, 7, 0x31 }, { 28, 0xffffffa, 7, 0x2f }, + { 28, 0xffffffa, 7, 0x2d }, { 28, 0xffffffa, 7, 0x2b }, + { 28, 0xffffffa, 7, 0x29 }, { 28, 0xffffffa, 7, 0x27 }, + { 28, 0xffffffa, 7, 0x25 }, { 28, 0xffffffa, 7, 0x23 }, + { 28, 0xffffffa, 7, 0x21 }, { 28, 0xffffffa, 7, 0x1f }, + { 28, 0xffffffa, 7, 0x1d }, { 28, 0xffffffa, 7, 0x1b }, + { 28, 0xffffffa, 7, 0x19 }, { 28, 0xffffffa, 7, 0x17 }, + { 28, 0xffffffa, 7, 0x15 }, { 28, 0xffffffa, 7, 0x13 }, + { 28, 0xffffffa, 7, 0x11 }, { 28, 0xffffffa, 7, 0xf }, + { 28, 0xffffffa, 7, 0xd }, { 28, 0xffffffa, 7, 0xb }, + { 28, 0xffffffa, 7, 0x9 }, { 28, 0xffffffa, 7, 0x7 }, + { 28, 0xffffffa, 7, 0x5 }, { 28, 0xffffffa, 7, 0x3 }, + { 28, 0xffffffa, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 7 zeroes + */ + { + { 9, 0x1f3, 0, 0 }, { 14, 0x3fef, 0, 0 }, + { 14, 0x3fed, 0, 0 }, { 18, 0x3ffdf, 0, 0 }, + { 18, 0x3ffdd, 0, 0 }, { 18, 0x3ffdb, 0, 0 }, + { 18, 0x3ffd9, 0, 0 }, { 22, 0x3fffbf, 0, 0 }, + { 22, 0x3fffbd, 0, 0 }, { 22, 0x3fffbb, 0, 0 }, + { 22, 0x3fffb9, 0, 0 }, { 22, 0x3fffb7, 0, 0 }, + { 22, 0x3fffb5, 0, 0 }, { 22, 0x3fffb3, 0, 0 }, + { 22, 0x3fffb1, 0, 0 }, { 27, 0x7ffff1f, 0, 0 }, + { 27, 0x7ffff1d, 0, 0 }, { 27, 0x7ffff1b, 0, 0 }, + { 27, 0x7ffff19, 0, 0 }, { 27, 0x7ffff17, 0, 0 }, + { 27, 0x7ffff15, 0, 0 }, { 27, 0x7ffff13, 0, 0 }, + { 27, 0x7ffff11, 0, 0 }, { 27, 0x7ffff0f, 0, 0 }, + { 27, 0x7ffff0d, 0, 0 }, { 27, 0x7ffff0b, 0, 0 }, + { 27, 0x7ffff09, 0, 0 }, { 27, 0x7ffff07, 0, 0 }, + { 27, 0x7ffff05, 0, 0 }, { 27, 0x7ffff03, 0, 0 }, + { 27, 0x7ffff01, 0, 0 }, { 31, 0x7ffffe7f, 0, 0 }, + { 31, 0x7ffffe7d, 0, 0 }, { 31, 0x7ffffe7b, 0, 0 }, + { 31, 0x7ffffe79, 0, 0 }, { 31, 0x7ffffe77, 0, 0 }, + { 31, 0x7ffffe75, 0, 0 }, { 31, 0x7ffffe73, 0, 0 }, + { 31, 0x7ffffe71, 0, 0 }, { 31, 0x7ffffe6f, 0, 0 }, + { 31, 0x7ffffe6d, 0, 0 }, { 31, 0x7ffffe6b, 0, 0 }, + { 31, 0x7ffffe69, 0, 0 }, { 31, 0x7ffffe67, 0, 0 }, + { 31, 0x7ffffe65, 0, 0 }, { 31, 0x7ffffe63, 0, 0 }, + { 31, 0x7ffffe61, 0, 0 }, { 31, 0x7ffffe5f, 0, 0 }, + { 31, 0x7ffffe5d, 0, 0 }, { 31, 0x7ffffe5b, 0, 0 }, + { 31, 0x7ffffe59, 0, 0 }, { 31, 0x7ffffe57, 0, 0 }, + { 31, 0x7ffffe55, 0, 0 }, { 31, 0x7ffffe53, 0, 0 }, + { 31, 0x7ffffe51, 0, 0 }, { 31, 0x7ffffe4f, 0, 0 }, + { 31, 0x7ffffe4d, 0, 0 }, { 31, 0x7ffffe4b, 0, 0 }, + { 31, 0x7ffffe49, 0, 0 }, { 31, 0x7ffffe47, 0, 0 }, + { 31, 0x7ffffe45, 0, 0 }, { 31, 0x7ffffe43, 0, 0 }, + { 31, 0x7ffffe41, 0, 0 }, { 28, 0xffffffb, 7, 0x7f }, + { 28, 0xffffffb, 7, 0x7d }, { 28, 0xffffffb, 7, 0x7b }, + { 28, 0xffffffb, 7, 0x79 }, { 28, 0xffffffb, 7, 0x77 }, + { 28, 0xffffffb, 7, 0x75 }, { 28, 0xffffffb, 7, 0x73 }, + { 28, 0xffffffb, 7, 0x71 }, { 28, 0xffffffb, 7, 0x6f }, + { 28, 0xffffffb, 7, 0x6d }, { 28, 0xffffffb, 7, 0x6b }, + { 28, 0xffffffb, 7, 0x69 }, { 28, 0xffffffb, 7, 0x67 }, + { 28, 0xffffffb, 7, 0x65 }, { 28, 0xffffffb, 7, 0x63 }, + { 28, 0xffffffb, 7, 0x61 }, { 28, 0xffffffb, 7, 0x5f }, + { 28, 0xffffffb, 7, 0x5d }, { 28, 0xffffffb, 7, 0x5b }, + { 28, 0xffffffb, 7, 0x59 }, { 28, 0xffffffb, 7, 0x57 }, + { 28, 0xffffffb, 7, 0x55 }, { 28, 0xffffffb, 7, 0x53 }, + { 28, 0xffffffb, 7, 0x51 }, { 28, 0xffffffb, 7, 0x4f }, + { 28, 0xffffffb, 7, 0x4d }, { 28, 0xffffffb, 7, 0x4b }, + { 28, 0xffffffb, 7, 0x49 }, { 28, 0xffffffb, 7, 0x47 }, + { 28, 0xffffffb, 7, 0x45 }, { 28, 0xffffffb, 7, 0x43 }, + { 28, 0xffffffb, 7, 0x41 }, { 28, 0xffffffb, 7, 0x3f }, + { 28, 0xffffffb, 7, 0x3d }, { 28, 0xffffffb, 7, 0x3b }, + { 28, 0xffffffb, 7, 0x39 }, { 28, 0xffffffb, 7, 0x37 }, + { 28, 0xffffffb, 7, 0x35 }, { 28, 0xffffffb, 7, 0x33 }, + { 28, 0xffffffb, 7, 0x31 }, { 28, 0xffffffb, 7, 0x2f }, + { 28, 0xffffffb, 7, 0x2d }, { 28, 0xffffffb, 7, 0x2b }, + { 28, 0xffffffb, 7, 0x29 }, { 28, 0xffffffb, 7, 0x27 }, + { 28, 0xffffffb, 7, 0x25 }, { 28, 0xffffffb, 7, 0x23 }, + { 28, 0xffffffb, 7, 0x21 }, { 28, 0xffffffb, 7, 0x1f }, + { 28, 0xffffffb, 7, 0x1d }, { 28, 0xffffffb, 7, 0x1b }, + { 28, 0xffffffb, 7, 0x19 }, { 28, 0xffffffb, 7, 0x17 }, + { 28, 0xffffffb, 7, 0x15 }, { 28, 0xffffffb, 7, 0x13 }, + { 28, 0xffffffb, 7, 0x11 }, { 28, 0xffffffb, 7, 0xf }, + { 28, 0xffffffb, 7, 0xd }, { 28, 0xffffffb, 7, 0xb }, + { 28, 0xffffffb, 7, 0x9 }, { 28, 0xffffffb, 7, 0x7 }, + { 28, 0xffffffb, 7, 0x5 }, { 28, 0xffffffb, 7, 0x3 }, + { 28, 0xffffffb, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 8 zeroes + */ + { + { 9, 0x1f5, 0, 0 }, { 15, 0x7fe3, 0, 0 }, + { 15, 0x7fe1, 0, 0 }, { 19, 0x7ffc7, 0, 0 }, + { 19, 0x7ffc5, 0, 0 }, { 19, 0x7ffc3, 0, 0 }, + { 19, 0x7ffc1, 0, 0 }, { 23, 0x7fff8f, 0, 0 }, + { 23, 0x7fff8d, 0, 0 }, { 23, 0x7fff8b, 0, 0 }, + { 23, 0x7fff89, 0, 0 }, { 23, 0x7fff87, 0, 0 }, + { 23, 0x7fff85, 0, 0 }, { 23, 0x7fff83, 0, 0 }, + { 23, 0x7fff81, 0, 0 }, { 27, 0x7ffff3f, 0, 0 }, + { 27, 0x7ffff3d, 0, 0 }, { 27, 0x7ffff3b, 0, 0 }, + { 27, 0x7ffff39, 0, 0 }, { 27, 0x7ffff37, 0, 0 }, + { 27, 0x7ffff35, 0, 0 }, { 27, 0x7ffff33, 0, 0 }, + { 27, 0x7ffff31, 0, 0 }, { 27, 0x7ffff2f, 0, 0 }, + { 27, 0x7ffff2d, 0, 0 }, { 27, 0x7ffff2b, 0, 0 }, + { 27, 0x7ffff29, 0, 0 }, { 27, 0x7ffff27, 0, 0 }, + { 27, 0x7ffff25, 0, 0 }, { 27, 0x7ffff23, 0, 0 }, + { 27, 0x7ffff21, 0, 0 }, { 31, 0x7ffffebf, 0, 0 }, + { 31, 0x7ffffebd, 0, 0 }, { 31, 0x7ffffebb, 0, 0 }, + { 31, 0x7ffffeb9, 0, 0 }, { 31, 0x7ffffeb7, 0, 0 }, + { 31, 0x7ffffeb5, 0, 0 }, { 31, 0x7ffffeb3, 0, 0 }, + { 31, 0x7ffffeb1, 0, 0 }, { 31, 0x7ffffeaf, 0, 0 }, + { 31, 0x7ffffead, 0, 0 }, { 31, 0x7ffffeab, 0, 0 }, + { 31, 0x7ffffea9, 0, 0 }, { 31, 0x7ffffea7, 0, 0 }, + { 31, 0x7ffffea5, 0, 0 }, { 31, 0x7ffffea3, 0, 0 }, + { 31, 0x7ffffea1, 0, 0 }, { 31, 0x7ffffe9f, 0, 0 }, + { 31, 0x7ffffe9d, 0, 0 }, { 31, 0x7ffffe9b, 0, 0 }, + { 31, 0x7ffffe99, 0, 0 }, { 31, 0x7ffffe97, 0, 0 }, + { 31, 0x7ffffe95, 0, 0 }, { 31, 0x7ffffe93, 0, 0 }, + { 31, 0x7ffffe91, 0, 0 }, { 31, 0x7ffffe8f, 0, 0 }, + { 31, 0x7ffffe8d, 0, 0 }, { 31, 0x7ffffe8b, 0, 0 }, + { 31, 0x7ffffe89, 0, 0 }, { 31, 0x7ffffe87, 0, 0 }, + { 31, 0x7ffffe85, 0, 0 }, { 31, 0x7ffffe83, 0, 0 }, + { 31, 0x7ffffe81, 0, 0 }, { 29, 0x1ffffff8, 7, 0x7f }, + { 29, 0x1ffffff8, 7, 0x7d }, { 29, 0x1ffffff8, 7, 0x7b }, + { 29, 0x1ffffff8, 7, 0x79 }, { 29, 0x1ffffff8, 7, 0x77 }, + { 29, 0x1ffffff8, 7, 0x75 }, { 29, 0x1ffffff8, 7, 0x73 }, + { 29, 0x1ffffff8, 7, 0x71 }, { 29, 0x1ffffff8, 7, 0x6f }, + { 29, 0x1ffffff8, 7, 0x6d }, { 29, 0x1ffffff8, 7, 0x6b }, + { 29, 0x1ffffff8, 7, 0x69 }, { 29, 0x1ffffff8, 7, 0x67 }, + { 29, 0x1ffffff8, 7, 0x65 }, { 29, 0x1ffffff8, 7, 0x63 }, + { 29, 0x1ffffff8, 7, 0x61 }, { 29, 0x1ffffff8, 7, 0x5f }, + { 29, 0x1ffffff8, 7, 0x5d }, { 29, 0x1ffffff8, 7, 0x5b }, + { 29, 0x1ffffff8, 7, 0x59 }, { 29, 0x1ffffff8, 7, 0x57 }, + { 29, 0x1ffffff8, 7, 0x55 }, { 29, 0x1ffffff8, 7, 0x53 }, + { 29, 0x1ffffff8, 7, 0x51 }, { 29, 0x1ffffff8, 7, 0x4f }, + { 29, 0x1ffffff8, 7, 0x4d }, { 29, 0x1ffffff8, 7, 0x4b }, + { 29, 0x1ffffff8, 7, 0x49 }, { 29, 0x1ffffff8, 7, 0x47 }, + { 29, 0x1ffffff8, 7, 0x45 }, { 29, 0x1ffffff8, 7, 0x43 }, + { 29, 0x1ffffff8, 7, 0x41 }, { 29, 0x1ffffff8, 7, 0x3f }, + { 29, 0x1ffffff8, 7, 0x3d }, { 29, 0x1ffffff8, 7, 0x3b }, + { 29, 0x1ffffff8, 7, 0x39 }, { 29, 0x1ffffff8, 7, 0x37 }, + { 29, 0x1ffffff8, 7, 0x35 }, { 29, 0x1ffffff8, 7, 0x33 }, + { 29, 0x1ffffff8, 7, 0x31 }, { 29, 0x1ffffff8, 7, 0x2f }, + { 29, 0x1ffffff8, 7, 0x2d }, { 29, 0x1ffffff8, 7, 0x2b }, + { 29, 0x1ffffff8, 7, 0x29 }, { 29, 0x1ffffff8, 7, 0x27 }, + { 29, 0x1ffffff8, 7, 0x25 }, { 29, 0x1ffffff8, 7, 0x23 }, + { 29, 0x1ffffff8, 7, 0x21 }, { 29, 0x1ffffff8, 7, 0x1f }, + { 29, 0x1ffffff8, 7, 0x1d }, { 29, 0x1ffffff8, 7, 0x1b }, + { 29, 0x1ffffff8, 7, 0x19 }, { 29, 0x1ffffff8, 7, 0x17 }, + { 29, 0x1ffffff8, 7, 0x15 }, { 29, 0x1ffffff8, 7, 0x13 }, + { 29, 0x1ffffff8, 7, 0x11 }, { 29, 0x1ffffff8, 7, 0xf }, + { 29, 0x1ffffff8, 7, 0xd }, { 29, 0x1ffffff8, 7, 0xb }, + { 29, 0x1ffffff8, 7, 0x9 }, { 29, 0x1ffffff8, 7, 0x7 }, + { 29, 0x1ffffff8, 7, 0x5 }, { 29, 0x1ffffff8, 7, 0x3 }, + { 29, 0x1ffffff8, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 9 zeroes + */ + { + { 11, 0x7f7, 0, 0 }, { 15, 0x7fe7, 0, 0 }, + { 15, 0x7fe5, 0, 0 }, { 19, 0x7ffcf, 0, 0 }, + { 19, 0x7ffcd, 0, 0 }, { 19, 0x7ffcb, 0, 0 }, + { 19, 0x7ffc9, 0, 0 }, { 23, 0x7fff9f, 0, 0 }, + { 23, 0x7fff9d, 0, 0 }, { 23, 0x7fff9b, 0, 0 }, + { 23, 0x7fff99, 0, 0 }, { 23, 0x7fff97, 0, 0 }, + { 23, 0x7fff95, 0, 0 }, { 23, 0x7fff93, 0, 0 }, + { 23, 0x7fff91, 0, 0 }, { 27, 0x7ffff5f, 0, 0 }, + { 27, 0x7ffff5d, 0, 0 }, { 27, 0x7ffff5b, 0, 0 }, + { 27, 0x7ffff59, 0, 0 }, { 27, 0x7ffff57, 0, 0 }, + { 27, 0x7ffff55, 0, 0 }, { 27, 0x7ffff53, 0, 0 }, + { 27, 0x7ffff51, 0, 0 }, { 27, 0x7ffff4f, 0, 0 }, + { 27, 0x7ffff4d, 0, 0 }, { 27, 0x7ffff4b, 0, 0 }, + { 27, 0x7ffff49, 0, 0 }, { 27, 0x7ffff47, 0, 0 }, + { 27, 0x7ffff45, 0, 0 }, { 27, 0x7ffff43, 0, 0 }, + { 27, 0x7ffff41, 0, 0 }, { 31, 0x7ffffeff, 0, 0 }, + { 31, 0x7ffffefd, 0, 0 }, { 31, 0x7ffffefb, 0, 0 }, + { 31, 0x7ffffef9, 0, 0 }, { 31, 0x7ffffef7, 0, 0 }, + { 31, 0x7ffffef5, 0, 0 }, { 31, 0x7ffffef3, 0, 0 }, + { 31, 0x7ffffef1, 0, 0 }, { 31, 0x7ffffeef, 0, 0 }, + { 31, 0x7ffffeed, 0, 0 }, { 31, 0x7ffffeeb, 0, 0 }, + { 31, 0x7ffffee9, 0, 0 }, { 31, 0x7ffffee7, 0, 0 }, + { 31, 0x7ffffee5, 0, 0 }, { 31, 0x7ffffee3, 0, 0 }, + { 31, 0x7ffffee1, 0, 0 }, { 31, 0x7ffffedf, 0, 0 }, + { 31, 0x7ffffedd, 0, 0 }, { 31, 0x7ffffedb, 0, 0 }, + { 31, 0x7ffffed9, 0, 0 }, { 31, 0x7ffffed7, 0, 0 }, + { 31, 0x7ffffed5, 0, 0 }, { 31, 0x7ffffed3, 0, 0 }, + { 31, 0x7ffffed1, 0, 0 }, { 31, 0x7ffffecf, 0, 0 }, + { 31, 0x7ffffecd, 0, 0 }, { 31, 0x7ffffecb, 0, 0 }, + { 31, 0x7ffffec9, 0, 0 }, { 31, 0x7ffffec7, 0, 0 }, + { 31, 0x7ffffec5, 0, 0 }, { 31, 0x7ffffec3, 0, 0 }, + { 31, 0x7ffffec1, 0, 0 }, { 29, 0x1ffffff9, 7, 0x7f }, + { 29, 0x1ffffff9, 7, 0x7d }, { 29, 0x1ffffff9, 7, 0x7b }, + { 29, 0x1ffffff9, 7, 0x79 }, { 29, 0x1ffffff9, 7, 0x77 }, + { 29, 0x1ffffff9, 7, 0x75 }, { 29, 0x1ffffff9, 7, 0x73 }, + { 29, 0x1ffffff9, 7, 0x71 }, { 29, 0x1ffffff9, 7, 0x6f }, + { 29, 0x1ffffff9, 7, 0x6d }, { 29, 0x1ffffff9, 7, 0x6b }, + { 29, 0x1ffffff9, 7, 0x69 }, { 29, 0x1ffffff9, 7, 0x67 }, + { 29, 0x1ffffff9, 7, 0x65 }, { 29, 0x1ffffff9, 7, 0x63 }, + { 29, 0x1ffffff9, 7, 0x61 }, { 29, 0x1ffffff9, 7, 0x5f }, + { 29, 0x1ffffff9, 7, 0x5d }, { 29, 0x1ffffff9, 7, 0x5b }, + { 29, 0x1ffffff9, 7, 0x59 }, { 29, 0x1ffffff9, 7, 0x57 }, + { 29, 0x1ffffff9, 7, 0x55 }, { 29, 0x1ffffff9, 7, 0x53 }, + { 29, 0x1ffffff9, 7, 0x51 }, { 29, 0x1ffffff9, 7, 0x4f }, + { 29, 0x1ffffff9, 7, 0x4d }, { 29, 0x1ffffff9, 7, 0x4b }, + { 29, 0x1ffffff9, 7, 0x49 }, { 29, 0x1ffffff9, 7, 0x47 }, + { 29, 0x1ffffff9, 7, 0x45 }, { 29, 0x1ffffff9, 7, 0x43 }, + { 29, 0x1ffffff9, 7, 0x41 }, { 29, 0x1ffffff9, 7, 0x3f }, + { 29, 0x1ffffff9, 7, 0x3d }, { 29, 0x1ffffff9, 7, 0x3b }, + { 29, 0x1ffffff9, 7, 0x39 }, { 29, 0x1ffffff9, 7, 0x37 }, + { 29, 0x1ffffff9, 7, 0x35 }, { 29, 0x1ffffff9, 7, 0x33 }, + { 29, 0x1ffffff9, 7, 0x31 }, { 29, 0x1ffffff9, 7, 0x2f }, + { 29, 0x1ffffff9, 7, 0x2d }, { 29, 0x1ffffff9, 7, 0x2b }, + { 29, 0x1ffffff9, 7, 0x29 }, { 29, 0x1ffffff9, 7, 0x27 }, + { 29, 0x1ffffff9, 7, 0x25 }, { 29, 0x1ffffff9, 7, 0x23 }, + { 29, 0x1ffffff9, 7, 0x21 }, { 29, 0x1ffffff9, 7, 0x1f }, + { 29, 0x1ffffff9, 7, 0x1d }, { 29, 0x1ffffff9, 7, 0x1b }, + { 29, 0x1ffffff9, 7, 0x19 }, { 29, 0x1ffffff9, 7, 0x17 }, + { 29, 0x1ffffff9, 7, 0x15 }, { 29, 0x1ffffff9, 7, 0x13 }, + { 29, 0x1ffffff9, 7, 0x11 }, { 29, 0x1ffffff9, 7, 0xf }, + { 29, 0x1ffffff9, 7, 0xd }, { 29, 0x1ffffff9, 7, 0xb }, + { 29, 0x1ffffff9, 7, 0x9 }, { 29, 0x1ffffff9, 7, 0x7 }, + { 29, 0x1ffffff9, 7, 0x5 }, { 29, 0x1ffffff9, 7, 0x3 }, + { 29, 0x1ffffff9, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 10 zeroes + */ + { + { 12, 0xff1, 0, 0 }, { 15, 0x7feb, 0, 0 }, + { 15, 0x7fe9, 0, 0 }, { 19, 0x7ffd7, 0, 0 }, + { 19, 0x7ffd5, 0, 0 }, { 19, 0x7ffd3, 0, 0 }, + { 19, 0x7ffd1, 0, 0 }, { 23, 0x7fffaf, 0, 0 }, + { 23, 0x7fffad, 0, 0 }, { 23, 0x7fffab, 0, 0 }, + { 23, 0x7fffa9, 0, 0 }, { 23, 0x7fffa7, 0, 0 }, + { 23, 0x7fffa5, 0, 0 }, { 23, 0x7fffa3, 0, 0 }, + { 23, 0x7fffa1, 0, 0 }, { 27, 0x7ffff7f, 0, 0 }, + { 27, 0x7ffff7d, 0, 0 }, { 27, 0x7ffff7b, 0, 0 }, + { 27, 0x7ffff79, 0, 0 }, { 27, 0x7ffff77, 0, 0 }, + { 27, 0x7ffff75, 0, 0 }, { 27, 0x7ffff73, 0, 0 }, + { 27, 0x7ffff71, 0, 0 }, { 27, 0x7ffff6f, 0, 0 }, + { 27, 0x7ffff6d, 0, 0 }, { 27, 0x7ffff6b, 0, 0 }, + { 27, 0x7ffff69, 0, 0 }, { 27, 0x7ffff67, 0, 0 }, + { 27, 0x7ffff65, 0, 0 }, { 27, 0x7ffff63, 0, 0 }, + { 27, 0x7ffff61, 0, 0 }, { 32, 0xfffffe3f, 0, 0 }, + { 32, 0xfffffe3d, 0, 0 }, { 32, 0xfffffe3b, 0, 0 }, + { 32, 0xfffffe39, 0, 0 }, { 32, 0xfffffe37, 0, 0 }, + { 32, 0xfffffe35, 0, 0 }, { 32, 0xfffffe33, 0, 0 }, + { 32, 0xfffffe31, 0, 0 }, { 32, 0xfffffe2f, 0, 0 }, + { 32, 0xfffffe2d, 0, 0 }, { 32, 0xfffffe2b, 0, 0 }, + { 32, 0xfffffe29, 0, 0 }, { 32, 0xfffffe27, 0, 0 }, + { 32, 0xfffffe25, 0, 0 }, { 32, 0xfffffe23, 0, 0 }, + { 32, 0xfffffe21, 0, 0 }, { 32, 0xfffffe1f, 0, 0 }, + { 32, 0xfffffe1d, 0, 0 }, { 32, 0xfffffe1b, 0, 0 }, + { 32, 0xfffffe19, 0, 0 }, { 32, 0xfffffe17, 0, 0 }, + { 32, 0xfffffe15, 0, 0 }, { 32, 0xfffffe13, 0, 0 }, + { 32, 0xfffffe11, 0, 0 }, { 32, 0xfffffe0f, 0, 0 }, + { 32, 0xfffffe0d, 0, 0 }, { 32, 0xfffffe0b, 0, 0 }, + { 32, 0xfffffe09, 0, 0 }, { 32, 0xfffffe07, 0, 0 }, + { 32, 0xfffffe05, 0, 0 }, { 32, 0xfffffe03, 0, 0 }, + { 32, 0xfffffe01, 0, 0 }, { 29, 0x1ffffffa, 7, 0x7f }, + { 29, 0x1ffffffa, 7, 0x7d }, { 29, 0x1ffffffa, 7, 0x7b }, + { 29, 0x1ffffffa, 7, 0x79 }, { 29, 0x1ffffffa, 7, 0x77 }, + { 29, 0x1ffffffa, 7, 0x75 }, { 29, 0x1ffffffa, 7, 0x73 }, + { 29, 0x1ffffffa, 7, 0x71 }, { 29, 0x1ffffffa, 7, 0x6f }, + { 29, 0x1ffffffa, 7, 0x6d }, { 29, 0x1ffffffa, 7, 0x6b }, + { 29, 0x1ffffffa, 7, 0x69 }, { 29, 0x1ffffffa, 7, 0x67 }, + { 29, 0x1ffffffa, 7, 0x65 }, { 29, 0x1ffffffa, 7, 0x63 }, + { 29, 0x1ffffffa, 7, 0x61 }, { 29, 0x1ffffffa, 7, 0x5f }, + { 29, 0x1ffffffa, 7, 0x5d }, { 29, 0x1ffffffa, 7, 0x5b }, + { 29, 0x1ffffffa, 7, 0x59 }, { 29, 0x1ffffffa, 7, 0x57 }, + { 29, 0x1ffffffa, 7, 0x55 }, { 29, 0x1ffffffa, 7, 0x53 }, + { 29, 0x1ffffffa, 7, 0x51 }, { 29, 0x1ffffffa, 7, 0x4f }, + { 29, 0x1ffffffa, 7, 0x4d }, { 29, 0x1ffffffa, 7, 0x4b }, + { 29, 0x1ffffffa, 7, 0x49 }, { 29, 0x1ffffffa, 7, 0x47 }, + { 29, 0x1ffffffa, 7, 0x45 }, { 29, 0x1ffffffa, 7, 0x43 }, + { 29, 0x1ffffffa, 7, 0x41 }, { 29, 0x1ffffffa, 7, 0x3f }, + { 29, 0x1ffffffa, 7, 0x3d }, { 29, 0x1ffffffa, 7, 0x3b }, + { 29, 0x1ffffffa, 7, 0x39 }, { 29, 0x1ffffffa, 7, 0x37 }, + { 29, 0x1ffffffa, 7, 0x35 }, { 29, 0x1ffffffa, 7, 0x33 }, + { 29, 0x1ffffffa, 7, 0x31 }, { 29, 0x1ffffffa, 7, 0x2f }, + { 29, 0x1ffffffa, 7, 0x2d }, { 29, 0x1ffffffa, 7, 0x2b }, + { 29, 0x1ffffffa, 7, 0x29 }, { 29, 0x1ffffffa, 7, 0x27 }, + { 29, 0x1ffffffa, 7, 0x25 }, { 29, 0x1ffffffa, 7, 0x23 }, + { 29, 0x1ffffffa, 7, 0x21 }, { 29, 0x1ffffffa, 7, 0x1f }, + { 29, 0x1ffffffa, 7, 0x1d }, { 29, 0x1ffffffa, 7, 0x1b }, + { 29, 0x1ffffffa, 7, 0x19 }, { 29, 0x1ffffffa, 7, 0x17 }, + { 29, 0x1ffffffa, 7, 0x15 }, { 29, 0x1ffffffa, 7, 0x13 }, + { 29, 0x1ffffffa, 7, 0x11 }, { 29, 0x1ffffffa, 7, 0xf }, + { 29, 0x1ffffffa, 7, 0xd }, { 29, 0x1ffffffa, 7, 0xb }, + { 29, 0x1ffffffa, 7, 0x9 }, { 29, 0x1ffffffa, 7, 0x7 }, + { 29, 0x1ffffffa, 7, 0x5 }, { 29, 0x1ffffffa, 7, 0x3 }, + { 29, 0x1ffffffa, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 11 zeroes + */ + { + { 12, 0xff3, 0, 0 }, { 15, 0x7fef, 0, 0 }, + { 15, 0x7fed, 0, 0 }, { 19, 0x7ffdf, 0, 0 }, + { 19, 0x7ffdd, 0, 0 }, { 19, 0x7ffdb, 0, 0 }, + { 19, 0x7ffd9, 0, 0 }, { 23, 0x7fffbf, 0, 0 }, + { 23, 0x7fffbd, 0, 0 }, { 23, 0x7fffbb, 0, 0 }, + { 23, 0x7fffb9, 0, 0 }, { 23, 0x7fffb7, 0, 0 }, + { 23, 0x7fffb5, 0, 0 }, { 23, 0x7fffb3, 0, 0 }, + { 23, 0x7fffb1, 0, 0 }, { 28, 0xfffff1f, 0, 0 }, + { 28, 0xfffff1d, 0, 0 }, { 28, 0xfffff1b, 0, 0 }, + { 28, 0xfffff19, 0, 0 }, { 28, 0xfffff17, 0, 0 }, + { 28, 0xfffff15, 0, 0 }, { 28, 0xfffff13, 0, 0 }, + { 28, 0xfffff11, 0, 0 }, { 28, 0xfffff0f, 0, 0 }, + { 28, 0xfffff0d, 0, 0 }, { 28, 0xfffff0b, 0, 0 }, + { 28, 0xfffff09, 0, 0 }, { 28, 0xfffff07, 0, 0 }, + { 28, 0xfffff05, 0, 0 }, { 28, 0xfffff03, 0, 0 }, + { 28, 0xfffff01, 0, 0 }, { 32, 0xfffffe7f, 0, 0 }, + { 32, 0xfffffe7d, 0, 0 }, { 32, 0xfffffe7b, 0, 0 }, + { 32, 0xfffffe79, 0, 0 }, { 32, 0xfffffe77, 0, 0 }, + { 32, 0xfffffe75, 0, 0 }, { 32, 0xfffffe73, 0, 0 }, + { 32, 0xfffffe71, 0, 0 }, { 32, 0xfffffe6f, 0, 0 }, + { 32, 0xfffffe6d, 0, 0 }, { 32, 0xfffffe6b, 0, 0 }, + { 32, 0xfffffe69, 0, 0 }, { 32, 0xfffffe67, 0, 0 }, + { 32, 0xfffffe65, 0, 0 }, { 32, 0xfffffe63, 0, 0 }, + { 32, 0xfffffe61, 0, 0 }, { 32, 0xfffffe5f, 0, 0 }, + { 32, 0xfffffe5d, 0, 0 }, { 32, 0xfffffe5b, 0, 0 }, + { 32, 0xfffffe59, 0, 0 }, { 32, 0xfffffe57, 0, 0 }, + { 32, 0xfffffe55, 0, 0 }, { 32, 0xfffffe53, 0, 0 }, + { 32, 0xfffffe51, 0, 0 }, { 32, 0xfffffe4f, 0, 0 }, + { 32, 0xfffffe4d, 0, 0 }, { 32, 0xfffffe4b, 0, 0 }, + { 32, 0xfffffe49, 0, 0 }, { 32, 0xfffffe47, 0, 0 }, + { 32, 0xfffffe45, 0, 0 }, { 32, 0xfffffe43, 0, 0 }, + { 32, 0xfffffe41, 0, 0 }, { 29, 0x1ffffffb, 7, 0x7f }, + { 29, 0x1ffffffb, 7, 0x7d }, { 29, 0x1ffffffb, 7, 0x7b }, + { 29, 0x1ffffffb, 7, 0x79 }, { 29, 0x1ffffffb, 7, 0x77 }, + { 29, 0x1ffffffb, 7, 0x75 }, { 29, 0x1ffffffb, 7, 0x73 }, + { 29, 0x1ffffffb, 7, 0x71 }, { 29, 0x1ffffffb, 7, 0x6f }, + { 29, 0x1ffffffb, 7, 0x6d }, { 29, 0x1ffffffb, 7, 0x6b }, + { 29, 0x1ffffffb, 7, 0x69 }, { 29, 0x1ffffffb, 7, 0x67 }, + { 29, 0x1ffffffb, 7, 0x65 }, { 29, 0x1ffffffb, 7, 0x63 }, + { 29, 0x1ffffffb, 7, 0x61 }, { 29, 0x1ffffffb, 7, 0x5f }, + { 29, 0x1ffffffb, 7, 0x5d }, { 29, 0x1ffffffb, 7, 0x5b }, + { 29, 0x1ffffffb, 7, 0x59 }, { 29, 0x1ffffffb, 7, 0x57 }, + { 29, 0x1ffffffb, 7, 0x55 }, { 29, 0x1ffffffb, 7, 0x53 }, + { 29, 0x1ffffffb, 7, 0x51 }, { 29, 0x1ffffffb, 7, 0x4f }, + { 29, 0x1ffffffb, 7, 0x4d }, { 29, 0x1ffffffb, 7, 0x4b }, + { 29, 0x1ffffffb, 7, 0x49 }, { 29, 0x1ffffffb, 7, 0x47 }, + { 29, 0x1ffffffb, 7, 0x45 }, { 29, 0x1ffffffb, 7, 0x43 }, + { 29, 0x1ffffffb, 7, 0x41 }, { 29, 0x1ffffffb, 7, 0x3f }, + { 29, 0x1ffffffb, 7, 0x3d }, { 29, 0x1ffffffb, 7, 0x3b }, + { 29, 0x1ffffffb, 7, 0x39 }, { 29, 0x1ffffffb, 7, 0x37 }, + { 29, 0x1ffffffb, 7, 0x35 }, { 29, 0x1ffffffb, 7, 0x33 }, + { 29, 0x1ffffffb, 7, 0x31 }, { 29, 0x1ffffffb, 7, 0x2f }, + { 29, 0x1ffffffb, 7, 0x2d }, { 29, 0x1ffffffb, 7, 0x2b }, + { 29, 0x1ffffffb, 7, 0x29 }, { 29, 0x1ffffffb, 7, 0x27 }, + { 29, 0x1ffffffb, 7, 0x25 }, { 29, 0x1ffffffb, 7, 0x23 }, + { 29, 0x1ffffffb, 7, 0x21 }, { 29, 0x1ffffffb, 7, 0x1f }, + { 29, 0x1ffffffb, 7, 0x1d }, { 29, 0x1ffffffb, 7, 0x1b }, + { 29, 0x1ffffffb, 7, 0x19 }, { 29, 0x1ffffffb, 7, 0x17 }, + { 29, 0x1ffffffb, 7, 0x15 }, { 29, 0x1ffffffb, 7, 0x13 }, + { 29, 0x1ffffffb, 7, 0x11 }, { 29, 0x1ffffffb, 7, 0xf }, + { 29, 0x1ffffffb, 7, 0xd }, { 29, 0x1ffffffb, 7, 0xb }, + { 29, 0x1ffffffb, 7, 0x9 }, { 29, 0x1ffffffb, 7, 0x7 }, + { 29, 0x1ffffffb, 7, 0x5 }, { 29, 0x1ffffffb, 7, 0x3 }, + { 29, 0x1ffffffb, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 12 zeroes + */ + { + { 12, 0xff5, 0, 0 }, { 16, 0xffe3, 0, 0 }, + { 16, 0xffe1, 0, 0 }, { 20, 0xfffc7, 0, 0 }, + { 20, 0xfffc5, 0, 0 }, { 20, 0xfffc3, 0, 0 }, + { 20, 0xfffc1, 0, 0 }, { 24, 0xffff8f, 0, 0 }, + { 24, 0xffff8d, 0, 0 }, { 24, 0xffff8b, 0, 0 }, + { 24, 0xffff89, 0, 0 }, { 24, 0xffff87, 0, 0 }, + { 24, 0xffff85, 0, 0 }, { 24, 0xffff83, 0, 0 }, + { 24, 0xffff81, 0, 0 }, { 28, 0xfffff3f, 0, 0 }, + { 28, 0xfffff3d, 0, 0 }, { 28, 0xfffff3b, 0, 0 }, + { 28, 0xfffff39, 0, 0 }, { 28, 0xfffff37, 0, 0 }, + { 28, 0xfffff35, 0, 0 }, { 28, 0xfffff33, 0, 0 }, + { 28, 0xfffff31, 0, 0 }, { 28, 0xfffff2f, 0, 0 }, + { 28, 0xfffff2d, 0, 0 }, { 28, 0xfffff2b, 0, 0 }, + { 28, 0xfffff29, 0, 0 }, { 28, 0xfffff27, 0, 0 }, + { 28, 0xfffff25, 0, 0 }, { 28, 0xfffff23, 0, 0 }, + { 28, 0xfffff21, 0, 0 }, { 32, 0xfffffebf, 0, 0 }, + { 32, 0xfffffebd, 0, 0 }, { 32, 0xfffffebb, 0, 0 }, + { 32, 0xfffffeb9, 0, 0 }, { 32, 0xfffffeb7, 0, 0 }, + { 32, 0xfffffeb5, 0, 0 }, { 32, 0xfffffeb3, 0, 0 }, + { 32, 0xfffffeb1, 0, 0 }, { 32, 0xfffffeaf, 0, 0 }, + { 32, 0xfffffead, 0, 0 }, { 32, 0xfffffeab, 0, 0 }, + { 32, 0xfffffea9, 0, 0 }, { 32, 0xfffffea7, 0, 0 }, + { 32, 0xfffffea5, 0, 0 }, { 32, 0xfffffea3, 0, 0 }, + { 32, 0xfffffea1, 0, 0 }, { 32, 0xfffffe9f, 0, 0 }, + { 32, 0xfffffe9d, 0, 0 }, { 32, 0xfffffe9b, 0, 0 }, + { 32, 0xfffffe99, 0, 0 }, { 32, 0xfffffe97, 0, 0 }, + { 32, 0xfffffe95, 0, 0 }, { 32, 0xfffffe93, 0, 0 }, + { 32, 0xfffffe91, 0, 0 }, { 32, 0xfffffe8f, 0, 0 }, + { 32, 0xfffffe8d, 0, 0 }, { 32, 0xfffffe8b, 0, 0 }, + { 32, 0xfffffe89, 0, 0 }, { 32, 0xfffffe87, 0, 0 }, + { 32, 0xfffffe85, 0, 0 }, { 32, 0xfffffe83, 0, 0 }, + { 32, 0xfffffe81, 0, 0 }, { 30, 0x1fff7400, 7, 0x7f }, + { 30, 0x1fff7400, 7, 0x7d }, { 30, 0x1fff7400, 7, 0x7b }, + { 30, 0x1fff7400, 7, 0x79 }, { 30, 0x1fff7400, 7, 0x77 }, + { 30, 0x1fff7400, 7, 0x75 }, { 30, 0x1fff7400, 7, 0x73 }, + { 30, 0x1fff7400, 7, 0x71 }, { 30, 0x1fff7400, 7, 0x6f }, + { 30, 0x1fff7400, 7, 0x6d }, { 30, 0x1fff7400, 7, 0x6b }, + { 30, 0x1fff7400, 7, 0x69 }, { 30, 0x1fff7400, 7, 0x67 }, + { 30, 0x1fff7400, 7, 0x65 }, { 30, 0x1fff7400, 7, 0x63 }, + { 30, 0x1fff7400, 7, 0x61 }, { 30, 0x1fff7400, 7, 0x5f }, + { 30, 0x1fff7400, 7, 0x5d }, { 30, 0x1fff7400, 7, 0x5b }, + { 30, 0x1fff7400, 7, 0x59 }, { 30, 0x1fff7400, 7, 0x57 }, + { 30, 0x1fff7400, 7, 0x55 }, { 30, 0x1fff7400, 7, 0x53 }, + { 30, 0x1fff7400, 7, 0x51 }, { 30, 0x1fff7400, 7, 0x4f }, + { 30, 0x1fff7400, 7, 0x4d }, { 30, 0x1fff7400, 7, 0x4b }, + { 30, 0x1fff7400, 7, 0x49 }, { 30, 0x1fff7400, 7, 0x47 }, + { 30, 0x1fff7400, 7, 0x45 }, { 30, 0x1fff7400, 7, 0x43 }, + { 30, 0x1fff7400, 7, 0x41 }, { 30, 0x1fff7400, 7, 0x3f }, + { 30, 0x1fff7400, 7, 0x3d }, { 30, 0x1fff7400, 7, 0x3b }, + { 30, 0x1fff7400, 7, 0x39 }, { 30, 0x1fff7400, 7, 0x37 }, + { 30, 0x1fff7400, 7, 0x35 }, { 30, 0x1fff7400, 7, 0x33 }, + { 30, 0x1fff7400, 7, 0x31 }, { 30, 0x1fff7400, 7, 0x2f }, + { 30, 0x1fff7400, 7, 0x2d }, { 30, 0x1fff7400, 7, 0x2b }, + { 30, 0x1fff7400, 7, 0x29 }, { 30, 0x1fff7400, 7, 0x27 }, + { 30, 0x1fff7400, 7, 0x25 }, { 30, 0x1fff7400, 7, 0x23 }, + { 30, 0x1fff7400, 7, 0x21 }, { 30, 0x1fff7400, 7, 0x1f }, + { 30, 0x1fff7400, 7, 0x1d }, { 30, 0x1fff7400, 7, 0x1b }, + { 30, 0x1fff7400, 7, 0x19 }, { 30, 0x1fff7400, 7, 0x17 }, + { 30, 0x1fff7400, 7, 0x15 }, { 30, 0x1fff7400, 7, 0x13 }, + { 30, 0x1fff7400, 7, 0x11 }, { 30, 0x1fff7400, 7, 0xf }, + { 30, 0x1fff7400, 7, 0xd }, { 30, 0x1fff7400, 7, 0xb }, + { 30, 0x1fff7400, 7, 0x9 }, { 30, 0x1fff7400, 7, 0x7 }, + { 30, 0x1fff7400, 7, 0x5 }, { 30, 0x1fff7400, 7, 0x3 }, + { 30, 0x1fff7400, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 13 zeroes + */ + { + { 12, 0xff7, 0, 0 }, { 16, 0xffe7, 0, 0 }, + { 16, 0xffe5, 0, 0 }, { 20, 0xfffcf, 0, 0 }, + { 20, 0xfffcd, 0, 0 }, { 20, 0xfffcb, 0, 0 }, + { 20, 0xfffc9, 0, 0 }, { 24, 0xffff9f, 0, 0 }, + { 24, 0xffff9d, 0, 0 }, { 24, 0xffff9b, 0, 0 }, + { 24, 0xffff99, 0, 0 }, { 24, 0xffff97, 0, 0 }, + { 24, 0xffff95, 0, 0 }, { 24, 0xffff93, 0, 0 }, + { 24, 0xffff91, 0, 0 }, { 28, 0xfffff5f, 0, 0 }, + { 28, 0xfffff5d, 0, 0 }, { 28, 0xfffff5b, 0, 0 }, + { 28, 0xfffff59, 0, 0 }, { 28, 0xfffff57, 0, 0 }, + { 28, 0xfffff55, 0, 0 }, { 28, 0xfffff53, 0, 0 }, + { 28, 0xfffff51, 0, 0 }, { 28, 0xfffff4f, 0, 0 }, + { 28, 0xfffff4d, 0, 0 }, { 28, 0xfffff4b, 0, 0 }, + { 28, 0xfffff49, 0, 0 }, { 28, 0xfffff47, 0, 0 }, + { 28, 0xfffff45, 0, 0 }, { 28, 0xfffff43, 0, 0 }, + { 28, 0xfffff41, 0, 0 }, { 32, 0xfffffeff, 0, 0 }, + { 32, 0xfffffefd, 0, 0 }, { 32, 0xfffffefb, 0, 0 }, + { 32, 0xfffffef9, 0, 0 }, { 32, 0xfffffef7, 0, 0 }, + { 32, 0xfffffef5, 0, 0 }, { 32, 0xfffffef3, 0, 0 }, + { 32, 0xfffffef1, 0, 0 }, { 32, 0xfffffeef, 0, 0 }, + { 32, 0xfffffeed, 0, 0 }, { 32, 0xfffffeeb, 0, 0 }, + { 32, 0xfffffee9, 0, 0 }, { 32, 0xfffffee7, 0, 0 }, + { 32, 0xfffffee5, 0, 0 }, { 32, 0xfffffee3, 0, 0 }, + { 32, 0xfffffee1, 0, 0 }, { 32, 0xfffffedf, 0, 0 }, + { 32, 0xfffffedd, 0, 0 }, { 32, 0xfffffedb, 0, 0 }, + { 32, 0xfffffed9, 0, 0 }, { 32, 0xfffffed7, 0, 0 }, + { 32, 0xfffffed5, 0, 0 }, { 32, 0xfffffed3, 0, 0 }, + { 32, 0xfffffed1, 0, 0 }, { 32, 0xfffffecf, 0, 0 }, + { 32, 0xfffffecd, 0, 0 }, { 32, 0xfffffecb, 0, 0 }, + { 32, 0xfffffec9, 0, 0 }, { 32, 0xfffffec7, 0, 0 }, + { 32, 0xfffffec5, 0, 0 }, { 32, 0xfffffec3, 0, 0 }, + { 32, 0xfffffec1, 0, 0 }, { 30, 0x3ffffff9, 7, 0x7f }, + { 30, 0x3ffffff9, 7, 0x7d }, { 30, 0x3ffffff9, 7, 0x7b }, + { 30, 0x3ffffff9, 7, 0x79 }, { 30, 0x3ffffff9, 7, 0x77 }, + { 30, 0x3ffffff9, 7, 0x75 }, { 30, 0x3ffffff9, 7, 0x73 }, + { 30, 0x3ffffff9, 7, 0x71 }, { 30, 0x3ffffff9, 7, 0x6f }, + { 30, 0x3ffffff9, 7, 0x6d }, { 30, 0x3ffffff9, 7, 0x6b }, + { 30, 0x3ffffff9, 7, 0x69 }, { 30, 0x3ffffff9, 7, 0x67 }, + { 30, 0x3ffffff9, 7, 0x65 }, { 30, 0x3ffffff9, 7, 0x63 }, + { 30, 0x3ffffff9, 7, 0x61 }, { 30, 0x3ffffff9, 7, 0x5f }, + { 30, 0x3ffffff9, 7, 0x5d }, { 30, 0x3ffffff9, 7, 0x5b }, + { 30, 0x3ffffff9, 7, 0x59 }, { 30, 0x3ffffff9, 7, 0x57 }, + { 30, 0x3ffffff9, 7, 0x55 }, { 30, 0x3ffffff9, 7, 0x53 }, + { 30, 0x3ffffff9, 7, 0x51 }, { 30, 0x3ffffff9, 7, 0x4f }, + { 30, 0x3ffffff9, 7, 0x4d }, { 30, 0x3ffffff9, 7, 0x4b }, + { 30, 0x3ffffff9, 7, 0x49 }, { 30, 0x3ffffff9, 7, 0x47 }, + { 30, 0x3ffffff9, 7, 0x45 }, { 30, 0x3ffffff9, 7, 0x43 }, + { 30, 0x3ffffff9, 7, 0x41 }, { 30, 0x3ffffff9, 7, 0x3f }, + { 30, 0x3ffffff9, 7, 0x3d }, { 30, 0x3ffffff9, 7, 0x3b }, + { 30, 0x3ffffff9, 7, 0x39 }, { 30, 0x3ffffff9, 7, 0x37 }, + { 30, 0x3ffffff9, 7, 0x35 }, { 30, 0x3ffffff9, 7, 0x33 }, + { 30, 0x3ffffff9, 7, 0x31 }, { 30, 0x3ffffff9, 7, 0x2f }, + { 30, 0x3ffffff9, 7, 0x2d }, { 30, 0x3ffffff9, 7, 0x2b }, + { 30, 0x3ffffff9, 7, 0x29 }, { 30, 0x3ffffff9, 7, 0x27 }, + { 30, 0x3ffffff9, 7, 0x25 }, { 30, 0x3ffffff9, 7, 0x23 }, + { 30, 0x3ffffff9, 7, 0x21 }, { 30, 0x3ffffff9, 7, 0x1f }, + { 30, 0x3ffffff9, 7, 0x1d }, { 30, 0x3ffffff9, 7, 0x1b }, + { 30, 0x3ffffff9, 7, 0x19 }, { 30, 0x3ffffff9, 7, 0x17 }, + { 30, 0x3ffffff9, 7, 0x15 }, { 30, 0x3ffffff9, 7, 0x13 }, + { 30, 0x3ffffff9, 7, 0x11 }, { 30, 0x3ffffff9, 7, 0xf }, + { 30, 0x3ffffff9, 7, 0xd }, { 30, 0x3ffffff9, 7, 0xb }, + { 30, 0x3ffffff9, 7, 0x9 }, { 30, 0x3ffffff9, 7, 0x7 }, + { 30, 0x3ffffff9, 7, 0x5 }, { 30, 0x3ffffff9, 7, 0x3 }, + { 30, 0x3ffffff9, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 14 zeroes + */ + { + { 13, 0x1ff1, 0, 0 }, { 16, 0xffeb, 0, 0 }, + { 16, 0xffe9, 0, 0 }, { 20, 0xfffd7, 0, 0 }, + { 20, 0xfffd5, 0, 0 }, { 20, 0xfffd3, 0, 0 }, + { 20, 0xfffd1, 0, 0 }, { 24, 0xffffaf, 0, 0 }, + { 24, 0xffffad, 0, 0 }, { 24, 0xffffab, 0, 0 }, + { 24, 0xffffa9, 0, 0 }, { 24, 0xffffa7, 0, 0 }, + { 24, 0xffffa5, 0, 0 }, { 24, 0xffffa3, 0, 0 }, + { 24, 0xffffa1, 0, 0 }, { 28, 0xfffff7f, 0, 0 }, + { 28, 0xfffff7d, 0, 0 }, { 28, 0xfffff7b, 0, 0 }, + { 28, 0xfffff79, 0, 0 }, { 28, 0xfffff77, 0, 0 }, + { 28, 0xfffff75, 0, 0 }, { 28, 0xfffff73, 0, 0 }, + { 28, 0xfffff71, 0, 0 }, { 28, 0xfffff6f, 0, 0 }, + { 28, 0xfffff6d, 0, 0 }, { 28, 0xfffff6b, 0, 0 }, + { 28, 0xfffff69, 0, 0 }, { 28, 0xfffff67, 0, 0 }, + { 28, 0xfffff65, 0, 0 }, { 28, 0xfffff63, 0, 0 }, + { 28, 0xfffff61, 0, 0 }, { 27, 0x7fffff8, 6, 0x3f }, + { 27, 0x7fffff8, 6, 0x3d }, { 27, 0x7fffff8, 6, 0x3b }, + { 27, 0x7fffff8, 6, 0x39 }, { 27, 0x7fffff8, 6, 0x37 }, + { 27, 0x7fffff8, 6, 0x35 }, { 27, 0x7fffff8, 6, 0x33 }, + { 27, 0x7fffff8, 6, 0x31 }, { 27, 0x7fffff8, 6, 0x2f }, + { 27, 0x7fffff8, 6, 0x2d }, { 27, 0x7fffff8, 6, 0x2b }, + { 27, 0x7fffff8, 6, 0x29 }, { 27, 0x7fffff8, 6, 0x27 }, + { 27, 0x7fffff8, 6, 0x25 }, { 27, 0x7fffff8, 6, 0x23 }, + { 27, 0x7fffff8, 6, 0x21 }, { 27, 0x7fffff8, 6, 0x1f }, + { 27, 0x7fffff8, 6, 0x1d }, { 27, 0x7fffff8, 6, 0x1b }, + { 27, 0x7fffff8, 6, 0x19 }, { 27, 0x7fffff8, 6, 0x17 }, + { 27, 0x7fffff8, 6, 0x15 }, { 27, 0x7fffff8, 6, 0x13 }, + { 27, 0x7fffff8, 6, 0x11 }, { 27, 0x7fffff8, 6, 0xf }, + { 27, 0x7fffff8, 6, 0xd }, { 27, 0x7fffff8, 6, 0xb }, + { 27, 0x7fffff8, 6, 0x9 }, { 27, 0x7fffff8, 6, 0x7 }, + { 27, 0x7fffff8, 6, 0x5 }, { 27, 0x7fffff8, 6, 0x3 }, + { 27, 0x7fffff8, 6, 0x1 }, { 30, 0x3ffffffa, 7, 0x7f }, + { 30, 0x3ffffffa, 7, 0x7d }, { 30, 0x3ffffffa, 7, 0x7b }, + { 30, 0x3ffffffa, 7, 0x79 }, { 30, 0x3ffffffa, 7, 0x77 }, + { 30, 0x3ffffffa, 7, 0x75 }, { 30, 0x3ffffffa, 7, 0x73 }, + { 30, 0x3ffffffa, 7, 0x71 }, { 30, 0x3ffffffa, 7, 0x6f }, + { 30, 0x3ffffffa, 7, 0x6d }, { 30, 0x3ffffffa, 7, 0x6b }, + { 30, 0x3ffffffa, 7, 0x69 }, { 30, 0x3ffffffa, 7, 0x67 }, + { 30, 0x3ffffffa, 7, 0x65 }, { 30, 0x3ffffffa, 7, 0x63 }, + { 30, 0x3ffffffa, 7, 0x61 }, { 30, 0x3ffffffa, 7, 0x5f }, + { 30, 0x3ffffffa, 7, 0x5d }, { 30, 0x3ffffffa, 7, 0x5b }, + { 30, 0x3ffffffa, 7, 0x59 }, { 30, 0x3ffffffa, 7, 0x57 }, + { 30, 0x3ffffffa, 7, 0x55 }, { 30, 0x3ffffffa, 7, 0x53 }, + { 30, 0x3ffffffa, 7, 0x51 }, { 30, 0x3ffffffa, 7, 0x4f }, + { 30, 0x3ffffffa, 7, 0x4d }, { 30, 0x3ffffffa, 7, 0x4b }, + { 30, 0x3ffffffa, 7, 0x49 }, { 30, 0x3ffffffa, 7, 0x47 }, + { 30, 0x3ffffffa, 7, 0x45 }, { 30, 0x3ffffffa, 7, 0x43 }, + { 30, 0x3ffffffa, 7, 0x41 }, { 30, 0x3ffffffa, 7, 0x3f }, + { 30, 0x3ffffffa, 7, 0x3d }, { 30, 0x3ffffffa, 7, 0x3b }, + { 30, 0x3ffffffa, 7, 0x39 }, { 30, 0x3ffffffa, 7, 0x37 }, + { 30, 0x3ffffffa, 7, 0x35 }, { 30, 0x3ffffffa, 7, 0x33 }, + { 30, 0x3ffffffa, 7, 0x31 }, { 30, 0x3ffffffa, 7, 0x2f }, + { 30, 0x3ffffffa, 7, 0x2d }, { 30, 0x3ffffffa, 7, 0x2b }, + { 30, 0x3ffffffa, 7, 0x29 }, { 30, 0x3ffffffa, 7, 0x27 }, + { 30, 0x3ffffffa, 7, 0x25 }, { 30, 0x3ffffffa, 7, 0x23 }, + { 30, 0x3ffffffa, 7, 0x21 }, { 30, 0x3ffffffa, 7, 0x1f }, + { 30, 0x3ffffffa, 7, 0x1d }, { 30, 0x3ffffffa, 7, 0x1b }, + { 30, 0x3ffffffa, 7, 0x19 }, { 30, 0x3ffffffa, 7, 0x17 }, + { 30, 0x3ffffffa, 7, 0x15 }, { 30, 0x3ffffffa, 7, 0x13 }, + { 30, 0x3ffffffa, 7, 0x11 }, { 30, 0x3ffffffa, 7, 0xf }, + { 30, 0x3ffffffa, 7, 0xd }, { 30, 0x3ffffffa, 7, 0xb }, + { 30, 0x3ffffffa, 7, 0x9 }, { 30, 0x3ffffffa, 7, 0x7 }, + { 30, 0x3ffffffa, 7, 0x5 }, { 30, 0x3ffffffa, 7, 0x3 }, + { 30, 0x3ffffffa, 7, 0x1 }, { 0, 0, 0, 0 } + }, + + /* + * prefixed with 15 zeroes + */ + { + { 13, 0x1ff3, 0, 0 }, { 2, 0x3, 0, 0 }, + { 2, 0x1, 0, 0 }, { 3, 0x7, 0, 0 }, + { 3, 0x5, 0, 0 }, { 3, 0x3, 0, 0 }, + { 3, 0x1, 0, 0 }, { 31, 0x7ffffffb, 4, 0xf }, + { 31, 0x7ffffffb, 4, 0xd }, { 31, 0x7ffffffb, 4, 0xb }, + { 31, 0x7ffffffb, 4, 0x9 }, { 31, 0x7ffffffb, 4, 0x7 }, + { 31, 0x7ffffffb, 4, 0x5 }, { 31, 0x7ffffffb, 4, 0x3 }, + { 31, 0x7ffffffb, 4, 0x1 }, { 5, 0x1f, 0, 0 }, + { 5, 0x1d, 0, 0 }, { 5, 0x1b, 0, 0 }, + { 5, 0x19, 0, 0 }, { 5, 0x17, 0, 0 }, + { 5, 0x15, 0, 0 }, { 5, 0x13, 0, 0 }, + { 5, 0x11, 0, 0 }, { 5, 0xf, 0, 0 }, + { 5, 0xd, 0, 0 }, { 5, 0xb, 0, 0 }, + { 5, 0x9, 0, 0 }, { 5, 0x7, 0, 0 }, + { 5, 0x5, 0, 0 }, { 5, 0x3, 0, 0 }, + { 5, 0x1, 0, 0 }, { 6, 0x3f, 0, 0 }, + { 6, 0x3d, 0, 0 }, { 6, 0x3b, 0, 0 }, + { 6, 0x39, 0, 0 }, { 6, 0x37, 0, 0 }, + { 6, 0x35, 0, 0 }, { 6, 0x33, 0, 0 }, + { 6, 0x31, 0, 0 }, { 6, 0x2f, 0, 0 }, + { 6, 0x2d, 0, 0 }, { 6, 0x2b, 0, 0 }, + { 6, 0x29, 0, 0 }, { 6, 0x27, 0, 0 }, + { 6, 0x25, 0, 0 }, { 6, 0x23, 0, 0 }, + { 6, 0x21, 0, 0 }, { 6, 0x1f, 0, 0 }, + { 6, 0x1d, 0, 0 }, { 6, 0x1b, 0, 0 }, + { 6, 0x19, 0, 0 }, { 6, 0x17, 0, 0 }, + { 6, 0x15, 0, 0 }, { 6, 0x13, 0, 0 }, + { 6, 0x11, 0, 0 }, { 6, 0xf, 0, 0 }, + { 6, 0xd, 0, 0 }, { 6, 0xb, 0, 0 }, + { 6, 0x9, 0, 0 }, { 6, 0x7, 0, 0 }, + { 6, 0x5, 0, 0 }, { 6, 0x3, 0, 0 }, + { 6, 0x1, 0, 0 }, { 7, 0x7f, 0, 0 }, + { 7, 0x7d, 0, 0 }, { 7, 0x7b, 0, 0 }, + { 7, 0x79, 0, 0 }, { 7, 0x77, 0, 0 }, + { 7, 0x75, 0, 0 }, { 7, 0x73, 0, 0 }, + { 7, 0x71, 0, 0 }, { 7, 0x6f, 0, 0 }, + { 7, 0x6d, 0, 0 }, { 7, 0x6b, 0, 0 }, + { 7, 0x69, 0, 0 }, { 7, 0x67, 0, 0 }, + { 7, 0x65, 0, 0 }, { 7, 0x63, 0, 0 }, + { 7, 0x61, 0, 0 }, { 7, 0x5f, 0, 0 }, + { 7, 0x5d, 0, 0 }, { 7, 0x5b, 0, 0 }, + { 7, 0x59, 0, 0 }, { 7, 0x57, 0, 0 }, + { 7, 0x55, 0, 0 }, { 7, 0x53, 0, 0 }, + { 7, 0x51, 0, 0 }, { 7, 0x4f, 0, 0 }, + { 7, 0x4d, 0, 0 }, { 7, 0x4b, 0, 0 }, + { 7, 0x49, 0, 0 }, { 7, 0x47, 0, 0 }, + { 7, 0x45, 0, 0 }, { 7, 0x43, 0, 0 }, + { 7, 0x41, 0, 0 }, { 7, 0x3f, 0, 0 }, + { 7, 0x3d, 0, 0 }, { 7, 0x3b, 0, 0 }, + { 7, 0x39, 0, 0 }, { 7, 0x37, 0, 0 }, + { 7, 0x35, 0, 0 }, { 7, 0x33, 0, 0 }, + { 7, 0x31, 0, 0 }, { 7, 0x2f, 0, 0 }, + { 7, 0x2d, 0, 0 }, { 7, 0x2b, 0, 0 }, + { 7, 0x29, 0, 0 }, { 7, 0x27, 0, 0 }, + { 7, 0x25, 0, 0 }, { 7, 0x23, 0, 0 }, + { 7, 0x21, 0, 0 }, { 7, 0x1f, 0, 0 }, + { 7, 0x1d, 0, 0 }, { 7, 0x1b, 0, 0 }, + { 7, 0x19, 0, 0 }, { 7, 0x17, 0, 0 }, + { 7, 0x15, 0, 0 }, { 7, 0x13, 0, 0 }, + { 7, 0x11, 0, 0 }, { 7, 0xf, 0, 0 }, + { 7, 0xd, 0, 0 }, { 7, 0xb, 0, 0 }, + { 7, 0x9, 0, 0 }, { 7, 0x7, 0, 0 }, + { 7, 0x5, 0, 0 }, { 7, 0x3, 0, 0 }, + { 7, 0x1, 0, 0 }, { 0, 0, 0, 0 } + } +}; + +VlcMagic _magic_values[] = { + { 0x0, 0, 1 }, + { 0x1, 0, 2 }, + { 0x4, 0, 3 }, + { 0xB, 1, 1 }, + { 0xC, 0, 4 }, + { 0x1A, 0, 5 }, + { 0x1B, 2, 1 }, + { 0x38, 3, 1 }, + { 0x39, 1, 2 }, + { 0x3A, 1, 3 }, + { 0x3B, 0, 6 }, + { 0x78, 4, 1 }, + { 0x79, 5, 1 }, + { 0x7A, 6, 1 }, + { 0x7B, 2, 2 }, + { 0xF8, 1, 4 }, + { 0xF9, 7, 1 }, + { 0xFA, 8, 1 }, + { 0xFB, 3, 2 }, + { 0x1F8, 4, 2 }, + { 0x1F9, 5, 2 }, + { 0x1FA, 2, 3 }, + { 0x1FB, 2, 4 }, + { 0x3F8, 1, 5 }, + { 0x3F9, 1, 6 }, + { 0x3FA, 0, 7 }, + { 0x3FB, 9, 1 }, + { 0x7F8, 10, 1 }, + { 0x7F9, 11, 1 }, + { 0x7FA, 12, 1 }, + { 0x7FB, 13, 1 }, + { 0xFF8, 14, 1 }, + { 0xFF9, 15, 1 }, + { 0xFFA, 6, 2 }, + { 0xFFB, 7, 2 }, + { 0x1FF8, 8, 2 }, + { 0x1FF9, 9, 2 }, + { 0x1FFA, 10, 2 }, + { 0x1FFB, 11, 2 }, + { 0x3FF8, 12, 2 }, + { 0x3FF9, 13, 2 }, + { 0x3FFA, 14, 2 }, + { 0x3FFB, 3, 3 }, + { 0x7FF8, 4, 3 }, + { 0x7FF9, 5, 3 }, + { 0x7FFA, 6, 3 }, + { 0x7FFB, 7, 3 }, + { 0xFFF8, 8, 3 }, + { 0xFFF9, 9, 3 }, + { 0xFFFA, 10, 3 }, + { 0xFFFB, 11, 3 }, + { 0x1FFF8, 12, 3 }, + { 0x1FFF9, 13, 3 }, + { 0x1FFFA, 14, 3 }, + { 0x1FFFB, 3, 4 }, + { 0x3FFF8, 4, 4 }, + { 0x3FFF9, 5, 4 }, + { 0x3FFFA, 6, 4 }, + { 0x3FFFB, 7, 4 }, + { 0x7FFF8, 8, 4 }, + { 0x7FFF9, 9, 4 }, + { 0x7FFFA, 10, 4 }, + { 0x7FFFB, 11, 4 }, + { 0xFFFF8, 12, 4 }, + { 0xFFFF9, 13, 4 }, + { 0xFFFFA, 14, 4 }, + { 0xFFFFB, 2, 5 }, + { 0x1FFFF8, 3, 5 }, + { 0x1FFFF9, 4, 5 }, + { 0x1FFFFA, 5, 5 }, + { 0x1FFFFB, 6, 5 }, + { 0x3FFFF8, 7, 5 }, + { 0x3FFFF9, 8, 5 }, + { 0x3FFFFA, 9, 5 }, + { 0x3FFFFB, 10, 5 }, + { 0x7FFFF8, 11, 5 }, + { 0x7FFFF9, 12, 5 }, + { 0x7FFFFA, 13, 5 }, + { 0x7FFFFB, 14, 5 }, + { 0xFFFFF8, 2, 6 }, + { 0xFFFFF9, 3, 6 }, + { 0xFFFFFA, 4, 6 }, + { 0xFFFFFB, 5, 6 }, + { 0x1FFFFF8, 6, 6 }, + { 0x1FFFFF9, 7, 6 }, + { 0x1FFFFFA, 8, 6 }, + { 0x1FFFFFB, 9, 6 }, + { 0x3FFFFF8, 10, 6 }, + { 0x3FFFFF9, 11, 6 }, + { 0x3FFFFFA, 12, 6 }, + { 0x3FFFFFB, 13, 6 }, + { 0x7FFFFF8, 14, 6 }, + { 0x7FFFFF9, 1, 7 }, + { 0x7FFFFFA, 2, 7 }, + { 0x7FFFFFB, 3, 7 }, + { 0xFFFFFF8, 4, 7 }, + { 0xFFFFFF9, 5, 7 }, + { 0xFFFFFFA, 6, 7 }, + { 0xFFFFFFB, 7, 7 }, + { 0x1FFFFFF8, 8, 7 }, + { 0x1FFFFFF9, 9, 7 }, + { 0x1FFFFFFA, 10, 7 }, + { 0x1FFFFFFB, 11, 7 }, + { 0x3FFFFFF8, 12, 7 }, + { 0x3FFFFFF9, 13, 7 }, + { 0x3FFFFFFA, 14, 7 } +}; + +/* + * _find_magic + * + * Internal helper-function used to locate a given + * VlcMagic entry. + */ +VlcMagic *_find_magic(guint magic) +{ + gint low = 0; + gint high = sizeof(_magic_values) / sizeof(VlcMagic) - 1; + gint mid; + + while (low <= high) { + mid = (low + high) / 2; + + if (_magic_values[mid].magic < magic) + low = mid + 1; + else if (_magic_values[mid].magic > magic) + high = mid - 1; + else + return &_magic_values[mid]; + } + + return NULL; +} + +/* + * _initialize_vlcdec_lookup + * + * Internal helper-function used to initialize + * the lookup-table used by the VLC-decoder. + */ +void _initialize_vlcdec_lookup(gint8 *lookup_tbl) +{ + gint8 util_buf[3072]; + gint v1_start, v1_end, v1_dec, util_buf_offset; + gint util_buf_offset_inc, buf1_val, samples_offset; + gint v1, v2; + gint8 *p, *p1, *p2, *p3; + + util_buf[0] = 0; + util_buf[1] = 0; + util_buf[2] = 0; + util_buf[3] = 1; + util_buf[4] = 1; + util_buf[5] = 1; + util_buf[765] = 1; + util_buf[766] = 0; + util_buf[767] = 1; + lookup_tbl[255] = 255; + lookup_tbl[256] = 1; + + v1_start = -3; + v1_dec = 4; + + util_buf_offset = 11; + util_buf_offset_inc = 12; + buf1_val = 2; + + samples_offset = 509; + + do { + v1 = v1_start; + v1_end = -(abs(v1_start) + 1) / 2; + v2 = 0; + + p2 = util_buf + util_buf_offset - 3; + + do { + p1 = util_buf + ((v1 & 0xff) * 3); + p1[0] = buf1_val; + p1[1] = v2; + p1[2] = buf1_val; + + p2[1] = buf1_val; + p2[2] = v2 + 1; + p2[3] = buf1_val; + + p3 = lookup_tbl + samples_offset + v2 + 1; + p3[0] = v1 & 0xff; + p3[1] = -(v1 & 0xff); + + v1++; + v2 += 2; + p2 -= 3; + } while (v1 <= v1_end); + + v1_start -= v1_dec; + v1_dec *= 2; + + util_buf_offset += util_buf_offset_inc; + util_buf_offset_inc *= 2; + buf1_val++; + + samples_offset += 255; + } while (buf1_val <= 7); + + p = lookup_tbl + 1785 + util_buf[388]; + p[0] = 129; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/vlc_decode.c b/kopete/protocols/msn/webcam/libmimic/vlc_decode.c new file mode 100644 index 00000000..5675342d --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/vlc_decode.c @@ -0,0 +1,119 @@ +/* Copyright (C) 2005 Ole Andr Vadla Ravns <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <string.h> +#include "mimic-private.h" + +extern guchar _col_zag[64]; + +/* + * _vlc_decode_block + * + * De-serialize (reconstruct) a variable length coded 8x8 block. + */ +gboolean _vlc_decode_block(MimCtx *ctx, gint *block, gint num_coeffs) +{ + guint pos; + + memset(block, 0, 64 * sizeof(gint)); + + /* The DC-value is read in as is. */ + block[0] = _read_bits(ctx, 8); + + for (pos = 1; pos < num_coeffs; pos++) { + + guint prev_data_index, prev_cur_chunk_len, prev_chunk; + guint value, num_bits; + gboolean prev_read_odd, found_magic; + + /* Save context. */ + prev_data_index = ctx->data_index; + prev_cur_chunk_len = ctx->cur_chunk_len; + prev_chunk = ctx->cur_chunk; + prev_read_odd = ctx->read_odd; + + /* Grab 16 bits. */ + value = _read_bits(ctx, 16) << 16; + + /* Restore context. */ + ctx->data_index = prev_data_index; + ctx->cur_chunk_len = prev_cur_chunk_len; + ctx->cur_chunk = prev_chunk; + ctx->read_odd = prev_read_odd; + + /* Analyze and determine number of bits to read initially. */ + num_bits = 3; + if ((value >> 30) == 0 || (value >> 30) == 1) { + num_bits = 2; + } else if ((value & 0xE0000000) != 0x80000000) { + guint nibble = value >> 28; + + if (nibble == 11 || nibble == 12) { + num_bits = 4; + } else if (nibble == 10) { + _read_bits(ctx, 4); + + return TRUE; + } else { + if (((value << 2) & 0x8000000) == 0) + num_bits = 2; + + num_bits += 2; + } + } + + /* Read that number of bits. */ + value = _read_bits(ctx, num_bits); + + /* + * Look up the current value against the magic ones, + * and continue extending it bit by bit from the input + * stream until the magic value is found or we have + * read 32 bits (in which case we give up). + */ + found_magic = FALSE; + while (!found_magic) { + VlcMagic *magic; + + if (num_bits > 32) + return FALSE; + + magic = _find_magic(value); + + if (magic != NULL) { + pos += magic->pos_add; + num_bits = magic->num_bits; + + found_magic = TRUE; + } else { + value <<= 1; + value |= _read_bits(ctx, 1); + + num_bits++; + } + } + + /* Read the number of bits given by magic value entry. */ + value = _read_bits(ctx, num_bits); + + /* Gotcha! :-) */ + block[_col_zag[pos]] = ctx->vlcdec_lookup[(num_bits * 255) + value]; + } + + return TRUE; +} + diff --git a/kopete/protocols/msn/webcam/libmimic/vlc_encode.c b/kopete/protocols/msn/webcam/libmimic/vlc_encode.c new file mode 100644 index 00000000..8d301627 --- /dev/null +++ b/kopete/protocols/msn/webcam/libmimic/vlc_encode.c @@ -0,0 +1,84 @@ +/* Copyright (C) 2005 Ole André Vadla Ravnås <oleavr@gmail.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <stdlib.h> +#include "mimic-private.h" + +extern guchar _col_zag[64]; +extern VlcSymbol _vlc_alphabet[16][128]; + +/* + * _vlc_encode_block + * + * Serialize an 8x8 block using variable length coding. + */ +void _vlc_encode_block(MimCtx *ctx, const gint *block, gint num_coeffs) +{ + gint i, num_zeroes; + + /* The DC value is written out as is. */ + _write_bits(ctx, block[0], 8); + + /* Number of zeroes prefixing the next non-zero value. */ + num_zeroes = 0; + + for (i = 1; i < num_coeffs && num_zeroes <= 14; i++) { + + /* Fetch AC coefficients from block in zig-zag order. */ + gint value = block[_col_zag[i]]; + + if (value != 0) { + VlcSymbol sym; + + /* Clip input values to [-128, +128]. */ + if (value < -128) + value = -128; + else if (value > 128) + value = 128; + + /* Look up symbol for the current non-zero value. */ + sym = _vlc_alphabet[num_zeroes][abs(value) - 1]; + + /* No symbol? very rare... */ + if (sym.length1 <= 0) + break; + + /* The symbols for negative values are the same as for positives, minus one. */ + if (value < 0) { + if (sym.length2 > 0) + sym.part2 -= 1; + else + sym.part1 -= 1; + } + + /* Write out the full symbol. */ + _write_bits(ctx, sym.part1, sym.length1); + if (sym.length2 > 0) + _write_bits(ctx, sym.part2, sym.length2); + + /* Start counting zeroes again. */ + num_zeroes = 0; + } else { + num_zeroes++; + } + } + + /* Write out EOB if necessary. */ + if (num_zeroes > 0) + _write_bits(ctx, 0xA, 4); +} + diff --git a/kopete/protocols/msn/webcam/mimicwrapper.cpp b/kopete/protocols/msn/webcam/mimicwrapper.cpp new file mode 100644 index 00000000..f7a43d93 --- /dev/null +++ b/kopete/protocols/msn/webcam/mimicwrapper.cpp @@ -0,0 +1,105 @@ +/* + Copyright (c) 2005 by Olivier Goffart <ogoffart@ kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + + +#include "mimicwrapper.h" + +#include "libmimic/mimic.h" + +//#include <qbytearray.h> +#include <kdebug.h> +#include <qimage.h> + +MimicWrapper::MimicWrapper() : m_init(false) +{ + m_mimctx=mimic_open(); +} + +MimicWrapper::~MimicWrapper() +{ + mimic_close(m_mimctx); +} + + +QPixmap MimicWrapper::decode(const QByteArray& data) +{ + if(!m_init) + { + if(!mimic_decoder_init(m_mimctx, (guchar*)(data.data()))) + { + kdWarning(14140) << k_funcinfo << "Impossible to init decoder" << endl; + return QPixmap(); + } + if (!mimic_get_property( m_mimctx, "buffer_size", &m_bufferSize) ) + { + kdWarning(14140) << k_funcinfo << "Impossible to get buffer size" << endl; + return QPixmap(); + } + m_init=true; + } + + QByteArray buff(m_bufferSize); + if(!mimic_decode_frame(m_mimctx, (guchar*)(data.data()) , (guchar*)(buff.data()) ) ) + { + kdWarning(14140) << k_funcinfo << "Impossible to decode frame" << endl; + return QPixmap(); + } + int width,height; + mimic_get_property(m_mimctx, "width", &width); + mimic_get_property(m_mimctx, "height", &height); + + + QByteArray buff2(m_bufferSize*4/3); + uint b2=0; + for(uint f=0;f<m_bufferSize;f+=3) + { + buff2[b2+0]=buff[f+2]; + buff2[b2+1]=buff[f+1]; + buff2[b2+2]=buff[f+0]; + buff2[b2+3]=0x00; + b2+=4; + } + + QImage img( (uchar*)(buff2.data()) , width , height , 32 , 0L , 0, QImage::BigEndian ); + return QPixmap(img); +} + +QByteArray MimicWrapper::encode(const QByteArray& data) +{ + if(!m_init) + { + if(!mimic_encoder_init(m_mimctx, MIMIC_RES_HIGH)) + { + kdWarning(14140) << k_funcinfo << "Impossible to init encoder" << endl; + return QByteArray(); + } + if (!mimic_get_property( m_mimctx, "buffer_size", &m_bufferSize) ) + { + kdWarning(14140) << k_funcinfo << "Impossible to get buffer size" << endl; + return QByteArray(); + } + m_init=true; + m_numFrames=0; + } + + QByteArray buff(m_bufferSize); + int buff_new_size; + if(!mimic_encode_frame(m_mimctx, (guchar*)(data.data()) , (guchar*)(buff.data()) , (gint*)(&buff_new_size) , m_numFrames%15==0 ) ) + { + kdWarning(14140) << k_funcinfo << "Impossible to decode frame" << endl; + return QByteArray(); + } + buff.resize(buff_new_size); + ++m_numFrames; + return buff; +} diff --git a/kopete/protocols/msn/webcam/mimicwrapper.h b/kopete/protocols/msn/webcam/mimicwrapper.h new file mode 100644 index 00000000..c4a7475f --- /dev/null +++ b/kopete/protocols/msn/webcam/mimicwrapper.h @@ -0,0 +1,40 @@ +/* + Copyright (c) 2005 by Olivier Goffart <ogoffart@ kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef MIMICWRAPPER_H +#define MIMICWREPPER_H + +#include <qpixmap.h> + +#include "kopete_export.h" + +typedef struct _MimCtx MimCtx; + +class KOPETE_EXPORT MimicWrapper +{ + public: + MimicWrapper(); + ~MimicWrapper(); + + QPixmap decode(const QByteArray &data); + QByteArray encode(const QByteArray &data); + + private: + MimCtx *m_mimctx; + bool m_init; + uint m_bufferSize; + uint m_numFrames; +}; + +#endif + diff --git a/kopete/protocols/msn/webcam/msnwebcamdialog.cpp b/kopete/protocols/msn/webcam/msnwebcamdialog.cpp new file mode 100644 index 00000000..092135f0 --- /dev/null +++ b/kopete/protocols/msn/webcam/msnwebcamdialog.cpp @@ -0,0 +1,82 @@ +/* + Kopete MSN Protocol + Copyright (c) 2005 by Olivier Goffart <ogoffart @kde.org> + + Note: this is just YahooWebcamDialog with s/Yahoo/MSN/g + + Copyright (c) 2005 by Matt Rogers <mattr@kde.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#include "msnwebcamdialog.h" + +#include <qframe.h> +#include <qobject.h> +#include <qwidget.h> +#include <kdebug.h> +#include <klocale.h> + + + +MSNWebcamDialog::MSNWebcamDialog( const QString& contact, QWidget * parent, const char * name ) + : KDialogBase( KDialogBase::Plain, i18n( "Webcam for %1" ).arg( contact ), + KDialogBase::Close, KDialogBase::Close, parent, name, false, true /*seperator*/ ), + m_imageContainer( this ) +{ + setInitialSize( QSize(320,290), true ); + + setEscapeButton( KDialogBase::Close ); + /* + QObject::connect( contact, SIGNAL( signalReceivedWebcamImage( const QPixmap& ) ), + this, SLOT( newImage( const QPixmap& ) ) ); + */ + QObject::connect( this, SIGNAL( closeClicked() ), this, SIGNAL( closingWebcamDialog() ) ); + /* + QObject::connect( contact, SIGNAL( webcamClosed( int ) ), this, SLOT( webcamClosed( int ) ) ); + */ + QFrame* page = plainPage(); + if ( page ) + { + kdDebug(14180) << k_funcinfo << "Adding webcam image container" << endl; + //m_imageContainer.setText( i18n( "No webcam image received" ) ); + //m_imageContainer.setAlignment( Qt::AlignCenter ); + m_imageContainer.setMinimumSize(320,240); + } + show(); +} + +MSNWebcamDialog::~ MSNWebcamDialog( ) +{ + +} + +void MSNWebcamDialog::newImage( const QPixmap & image ) +{ + kdDebug(14180) << k_funcinfo << "New image received" << endl; + // kdDebug(14180) << image << endl; + //m_imageContainer.clear(); + m_imageContainer.updatePixmap( image ); + //show(); +} + +void MSNWebcamDialog::webcamClosed( int reason ) +{ + kdDebug(14180) << k_funcinfo << "webcam closed with reason?? " << reason <<endl; + //m_imageContainer.clear(); + //m_imageContainer.setText( i18n( "Webcam closed with reason %1" ).arg( QString::number( reason ) ) ); + //m_imageContainer.setAlignment( Qt::AlignCenter ); + //show(); +} + +// kate: indent-mode csands; tab-width 4; + +#include "msnwebcamdialog.moc" diff --git a/kopete/protocols/msn/webcam/msnwebcamdialog.h b/kopete/protocols/msn/webcam/msnwebcamdialog.h new file mode 100644 index 00000000..dc10285d --- /dev/null +++ b/kopete/protocols/msn/webcam/msnwebcamdialog.h @@ -0,0 +1,55 @@ +/* + Kopete MSN Protocol + + Copyright (c) 2005 by Olivier Goffart <ogoffart @kde.org> + + Note: this is just YahooWebcamDialog with s/Yahoo/MSN/g + + Copyright (c) 2005 by Matt Rogers <mattr@kde.org> + Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org> + + ************************************************************************* + * * + * 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. * + * * + ************************************************************************* +*/ + +#ifndef YAHOOWEBCAMDIALOG_H_ +#define YAHOOWEBCAMDIALOG_H_ + +//#include <qlabel.h> +#include <webcamwidget.h> +#include <kdialogbase.h> + +#include "kopete_export.h" + + +class QPixmap; +class QWidget; +class MSNContact; + +class KOPETE_EXPORT MSNWebcamDialog : public KDialogBase +{ +Q_OBJECT +public: + MSNWebcamDialog( const QString& contact, QWidget* parent = 0, const char* name = 0 ); + ~MSNWebcamDialog(); + +public slots: + void newImage( const QPixmap& image ); + void webcamClosed( int ); + +signals: + void closingWebcamDialog(); + +private: + Kopete::WebcamWidget m_imageContainer; + +}; + +#endif +//kate: indent-mode csands; auto-insert-doxygen on; |