summaryrefslogtreecommitdiffstats
path: root/kopete/protocols/msn
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commitbcb704366cb5e333a626c18c308c7e0448a8e69f (patch)
treef0d6ab7d78ecdd9207cf46536376b44b91a1ca71 /kopete/protocols/msn
downloadtdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.tar.gz
tdenetwork-bcb704366cb5e333a626c18c308c7e0448a8e69f.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdenetwork@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kopete/protocols/msn')
-rw-r--r--kopete/protocols/msn/Changelog106
-rw-r--r--kopete/protocols/msn/Makefile.am46
-rw-r--r--kopete/protocols/msn/ReleaseNotes31
-rw-r--r--kopete/protocols/msn/TODO5
-rw-r--r--kopete/protocols/msn/config/Makefile.am12
-rw-r--r--kopete/protocols/msn/config/kopete_msn_config.desktop123
-rw-r--r--kopete/protocols/msn/config/msnpreferences.cpp33
-rw-r--r--kopete/protocols/msn/config/msnprefs.ui217
-rw-r--r--kopete/protocols/msn/dispatcher.cpp647
-rw-r--r--kopete/protocols/msn/dispatcher.h107
-rw-r--r--kopete/protocols/msn/icons/Makefile.am2
-rw-r--r--kopete/protocols/msn/icons/cr128-app-msn_protocol.pngbin0 -> 16158 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_away.pngbin0 -> 661 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_blocked.pngbin0 -> 292 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_brb.pngbin0 -> 449 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_busy.pngbin0 -> 588 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_connecting.mngbin0 -> 4503 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_invisible.pngbin0 -> 687 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_lunch.pngbin0 -> 497 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_na.pngbin0 -> 387 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_newmsg.pngbin0 -> 688 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_offline.pngbin0 -> 819 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_online.pngbin0 -> 943 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-action-msn_phone.pngbin0 -> 523 bytes
-rw-r--r--kopete/protocols/msn/icons/cr16-app-msn_protocol.pngbin0 -> 943 bytes
-rw-r--r--kopete/protocols/msn/icons/cr32-app-msn_protocol.pngbin0 -> 2399 bytes
-rw-r--r--kopete/protocols/msn/icons/cr48-app-msn_protocol.pngbin0 -> 4758 bytes
-rw-r--r--kopete/protocols/msn/icons/cr64-app-msn_protocol.pngbin0 -> 7152 bytes
-rw-r--r--kopete/protocols/msn/incomingtransfer.cpp381
-rw-r--r--kopete/protocols/msn/incomingtransfer.h57
-rw-r--r--kopete/protocols/msn/kopete_msn.desktop99
-rw-r--r--kopete/protocols/msn/messageformatter.cpp192
-rw-r--r--kopete/protocols/msn/messageformatter.h40
-rw-r--r--kopete/protocols/msn/msnaccount.cpp1499
-rw-r--r--kopete/protocols/msn/msnaccount.h270
-rw-r--r--kopete/protocols/msn/msnaddcontactpage.cpp85
-rw-r--r--kopete/protocols/msn/msnaddcontactpage.h33
-rw-r--r--kopete/protocols/msn/msnchallengehandler.cpp151
-rw-r--r--kopete/protocols/msn/msnchallengehandler.h64
-rw-r--r--kopete/protocols/msn/msnchatsession.cpp775
-rw-r--r--kopete/protocols/msn/msnchatsession.h140
-rw-r--r--kopete/protocols/msn/msnchatui.rc27
-rw-r--r--kopete/protocols/msn/msncontact.cpp713
-rw-r--r--kopete/protocols/msn/msncontact.h199
-rw-r--r--kopete/protocols/msn/msndebugrawcmddlg.cpp73
-rw-r--r--kopete/protocols/msn/msndebugrawcmddlg.h53
-rw-r--r--kopete/protocols/msn/msnfiletransfersocket.cpp481
-rw-r--r--kopete/protocols/msn/msnfiletransfersocket.h119
-rw-r--r--kopete/protocols/msn/msninvitation.cpp100
-rw-r--r--kopete/protocols/msn/msninvitation.h126
-rw-r--r--kopete/protocols/msn/msnnotifysocket.cpp1309
-rw-r--r--kopete/protocols/msn/msnnotifysocket.h216
-rw-r--r--kopete/protocols/msn/msnprotocol.cpp179
-rw-r--r--kopete/protocols/msn/msnprotocol.h187
-rw-r--r--kopete/protocols/msn/msnsecureloginhandler.cpp131
-rw-r--r--kopete/protocols/msn/msnsecureloginhandler.h76
-rw-r--r--kopete/protocols/msn/msnsocket.cpp1099
-rw-r--r--kopete/protocols/msn/msnsocket.h362
-rw-r--r--kopete/protocols/msn/msnswitchboardsocket.cpp1142
-rw-r--r--kopete/protocols/msn/msnswitchboardsocket.h166
-rw-r--r--kopete/protocols/msn/outgoingtransfer.cpp432
-rw-r--r--kopete/protocols/msn/outgoingtransfer.h59
-rw-r--r--kopete/protocols/msn/p2p.cpp412
-rw-r--r--kopete/protocols/msn/p2p.h147
-rw-r--r--kopete/protocols/msn/sha1.cpp192
-rw-r--r--kopete/protocols/msn/sha1.h59
-rw-r--r--kopete/protocols/msn/transport.cpp356
-rw-r--r--kopete/protocols/msn/transport.h167
-rw-r--r--kopete/protocols/msn/ui/Makefile.am12
-rw-r--r--kopete/protocols/msn/ui/msnadd.ui97
-rw-r--r--kopete/protocols/msn/ui/msndebugrawcommand_base.ui107
-rw-r--r--kopete/protocols/msn/ui/msneditaccountui.ui1421
-rw-r--r--kopete/protocols/msn/ui/msneditaccountwidget.cpp369
-rw-r--r--kopete/protocols/msn/ui/msneditaccountwidget.h59
-rw-r--r--kopete/protocols/msn/ui/msninfo.ui221
-rw-r--r--kopete/protocols/msn/webcam.cpp891
-rw-r--r--kopete/protocols/msn/webcam.h91
-rw-r--r--kopete/protocols/msn/webcam/Makefile.am14
-rw-r--r--kopete/protocols/msn/webcam/libmimic/AUTHORS2
-rw-r--r--kopete/protocols/msn/webcam/libmimic/COPYING504
-rw-r--r--kopete/protocols/msn/webcam/libmimic/Makefile.am24
-rw-r--r--kopete/protocols/msn/webcam/libmimic/README40
-rw-r--r--kopete/protocols/msn/webcam/libmimic/bitstring.c88
-rw-r--r--kopete/protocols/msn/webcam/libmimic/colorspace.c161
-rw-r--r--kopete/protocols/msn/webcam/libmimic/deblock.c450
-rw-r--r--kopete/protocols/msn/webcam/libmimic/decode.c311
-rw-r--r--kopete/protocols/msn/webcam/libmimic/encode.c419
-rw-r--r--kopete/protocols/msn/webcam/libmimic/fdct_quant.c181
-rw-r--r--kopete/protocols/msn/webcam/libmimic/idct_dequant.c134
-rw-r--r--kopete/protocols/msn/webcam/libmimic/mimic-private.h117
-rw-r--r--kopete/protocols/msn/webcam/libmimic/mimic.c334
-rw-r--r--kopete/protocols/msn/webcam/libmimic/mimic.h73
-rw-r--r--kopete/protocols/msn/webcam/libmimic/query.c1
-rw-r--r--kopete/protocols/msn/webcam/libmimic/vlc_common.c1364
-rw-r--r--kopete/protocols/msn/webcam/libmimic/vlc_decode.c119
-rw-r--r--kopete/protocols/msn/webcam/libmimic/vlc_encode.c84
-rw-r--r--kopete/protocols/msn/webcam/mimicwrapper.cpp105
-rw-r--r--kopete/protocols/msn/webcam/mimicwrapper.h40
-rw-r--r--kopete/protocols/msn/webcam/msnwebcamdialog.cpp82
-rw-r--r--kopete/protocols/msn/webcam/msnwebcamdialog.h55
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>&amp;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>&amp;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 &amp;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
new file mode 100644
index 00000000..dc94a4e9
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr128-app-msn_protocol.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_away.png b/kopete/protocols/msn/icons/cr16-action-msn_away.png
new file mode 100644
index 00000000..cbbd45fc
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_away.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_blocked.png b/kopete/protocols/msn/icons/cr16-action-msn_blocked.png
new file mode 100644
index 00000000..80efc4c7
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_blocked.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_brb.png b/kopete/protocols/msn/icons/cr16-action-msn_brb.png
new file mode 100644
index 00000000..3f1a0d30
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_brb.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_busy.png b/kopete/protocols/msn/icons/cr16-action-msn_busy.png
new file mode 100644
index 00000000..b3dcac08
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_busy.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng b/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng
new file mode 100644
index 00000000..38629273
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_connecting.mng
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_invisible.png b/kopete/protocols/msn/icons/cr16-action-msn_invisible.png
new file mode 100644
index 00000000..ce42bef0
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_invisible.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_lunch.png b/kopete/protocols/msn/icons/cr16-action-msn_lunch.png
new file mode 100644
index 00000000..abf42e3f
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_lunch.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_na.png b/kopete/protocols/msn/icons/cr16-action-msn_na.png
new file mode 100644
index 00000000..b1aa91af
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_na.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png b/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png
new file mode 100644
index 00000000..d42bb0ae
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_newmsg.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_offline.png b/kopete/protocols/msn/icons/cr16-action-msn_offline.png
new file mode 100644
index 00000000..5cf9ffd5
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_offline.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_online.png b/kopete/protocols/msn/icons/cr16-action-msn_online.png
new file mode 100644
index 00000000..71169ad2
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_online.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-action-msn_phone.png b/kopete/protocols/msn/icons/cr16-action-msn_phone.png
new file mode 100644
index 00000000..857ec14a
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-action-msn_phone.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr16-app-msn_protocol.png b/kopete/protocols/msn/icons/cr16-app-msn_protocol.png
new file mode 100644
index 00000000..a18ff5d4
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr16-app-msn_protocol.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr32-app-msn_protocol.png b/kopete/protocols/msn/icons/cr32-app-msn_protocol.png
new file mode 100644
index 00000000..2c9b130b
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr32-app-msn_protocol.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr48-app-msn_protocol.png b/kopete/protocols/msn/icons/cr48-app-msn_protocol.png
new file mode 100644
index 00000000..ad495100
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr48-app-msn_protocol.png
Binary files differ
diff --git a/kopete/protocols/msn/icons/cr64-app-msn_protocol.png b/kopete/protocols/msn/icons/cr64-app-msn_protocol.png
new file mode 100644
index 00000000..338f81bf
--- /dev/null
+++ b/kopete/protocols/msn/icons/cr64-app-msn_protocol.png
Binary files differ
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>&amp;Chat</text>
+ <Action name="msnWebcamReceive" />
+ <Action name="msnWebcamSend" />
+ <Action name="msnSendFile" />
+ <Action name="msnSendNudge" />
+ <Action name="msnRequestDisplayPicture" />
+ <Action name="msnInvite" />
+<!-- <Menu name="debug">
+ <text>&amp;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 &amp;
+ notificationDOMAsString.replace(rx, "&amp;");
+ 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>&amp;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>&lt;i&gt;(for example: joe@hotmail.com)&lt;/i&gt;</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>&amp;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&amp;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 &amp;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 &amp;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>&amp;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.&lt;br&gt;&lt;br&gt;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&amp;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>&amp;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&amp;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&amp;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 &amp;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>&lt;qt&gt;&lt;b&gt;Note:&lt;/b&gt; 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&amp;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>&lt;qt&gt;&lt;p&gt;Indicate when Kopete will download the display pictures of contacts&lt;/p&gt;
+&lt;dl&gt;&lt;dt&gt;Only manually&lt;/dt&gt;&lt;dd&gt;The picture is not downloaded automatically. It is only downloaded when the user requests it&lt;/dd&gt;
+&lt;dt&gt;When a chat is open&lt;/dt&gt;&lt;dd&gt;The picture is downloaded when a conversation socket is opened, i.e. when you open a chat window&lt;/dd&gt;
+&lt;dt&gt;Automatically&lt;/dt&gt;&lt;dd&gt;Always try to download the picture if the contact has one. &lt;b&gt;Note:&lt;/b&gt; this will open a socket, and let the user know you are downloading their picture.&lt;/dd&gt;&lt;/dl&gt;</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>&lt;qt&gt;&lt;p&gt;Indicate when Kopete will download the pictures of contacts&lt;/p&gt;
+&lt;dl&gt;&lt;dt&gt;Only manually&lt;/dt&gt;&lt;dd&gt;The picture is not downloaded automatically. It is only downloaded when the user requests it&lt;/dd&gt;
+&lt;dt&gt;When a chat is open&lt;/dt&gt;&lt;dd&gt;The picture is downloaded when a conversation socket is opened, i.e. when you open a chat window&lt;/dd&gt;
+&lt;dt&gt;Automatically&lt;/dt&gt;&lt;dd&gt;Always try to download the picture if the contact has one. &lt;b&gt;Note:&lt;/b&gt; this will open a socket, and let the user know you are downloading their picture.&lt;/dd&gt;&lt;/dl&gt;</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>useCustomEmoticons</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;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&amp;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>&lt;qt&gt;Make it possible for your contacts to detect if you are using Kopete.&lt;br&gt;We recommend leaving this checked.&lt;/qt&gt;</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 &amp;typing notifications</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>&lt;qt&gt;Check this box to send &lt;b&gt;Typing notifications&lt;/b&gt; 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.&lt;/qt&gt;</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 &amp;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>&amp;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&amp;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>&amp;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>&amp;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&amp;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>&amp;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&amp;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>&lt;i&gt;Italics&lt;/i&gt; contacts are not on your contact list.&lt;br&gt;
+&lt;br&gt;
+&lt;b&gt;Bold&lt;/b&gt; 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&amp;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>&amp;&gt;</string>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>m_allowButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;&lt;</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&amp;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' &amp;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 &amp;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&amp;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>&amp;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&amp;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&amp;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 &amp;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&amp;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 &amp;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 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 "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 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"
+
+
+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 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"
+
+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 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"
+
+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;