Index: network/applications/filetransferp2p.cpp
===================================================================
--- network/applications/filetransferp2p.cpp	(revision 3566)
+++ network/applications/filetransferp2p.cpp	(working copy)
@@ -473,9 +473,6 @@
             << ( usePreview ? "with thumbnail." : "without thumbnail." );
 #endif
 
-  // Create the short name
-  uint shortNameLength = fileName_.length();
-
   // Get the file data (preview
   ulong filesize  = fileData->size();
   int flags       = (hasPreview ? 0 : 1);
@@ -493,14 +490,7 @@
   P2PMessage::insertBytes( context, flags,    16 );  // Field 4: 1 if NO preview data
 
   // Field 5: the file name
-  const unsigned short * utf16Name = fileName_.utf16();
-  int offset = 20;
-  for(uint i = 0; i < shortNameLength; ++i)
-  {
-    P2PMessage::insertShortBytes(context, utf16Name[i], offset);
-    offset += 2;
-  }
-
+  P2PMessage::insertUtf16String( context, fileName_, 20 );
   P2PMessage::insertBytes(context, 0xFFFFFFFF, 570); // Field 6: some splitter field.
 
   // Insert preview data
@@ -836,6 +826,7 @@
 }
 
 
+
 /**
  * @brief Called when the thumbnail is generated.
  */
@@ -843,14 +834,10 @@
 {
   bool hasPreview = thumbnailProvider_->isSuccessful();
 
-  // Create the session id and context field
-  QString context   = createContextField( file_, hasPreview );
-  uint    sessionID = (uint)KMessShared::generateID();
-
   // Send the invitation
-  sendSlpSessionInvitation(sessionID, getAppId(), 2, context);
+  QString context = createContextField( file_, hasPreview );
+  sendSlpSessionInvitation( KMessShared::generateID(), getAppId(), 2, context );
 
-
   // Generate the HTML to cancel the transfer
   QString html = i18n( "Sending file &quot;%1&quot; (%2).",
                        "<span class=\"filename invitationFilename\">" + fileName_ + "</span>",
Index: network/applications/p2papplicationbase.cpp
===================================================================
--- network/applications/p2papplicationbase.cpp	(revision 3566)
+++ network/applications/p2papplicationbase.cpp	(working copy)
@@ -286,7 +286,7 @@
     }
 
     // Also attempt to match on the messageID alone if:
-    // - it's a 0x80 message. In this case sendP2PMessage() has overwritten the previous uniqueID.
+    // - it's a 0x80 message. In this case sendP2PMessageImpl() has overwritten the previous uniqueID.
     // - the UniqueID is not set. (also happens with normal ack messages in GAIM 1.5).
     //
     // This happens with 0x01 control messages:
@@ -863,15 +863,15 @@
       shouldSendAck_ = true;
       gotDataPreparation(p2pMessage);
     }
-    // Check for data messages
-    else if(p2pMessage.isMsnObjectData()
-         || p2pMessage.isFileData())
+    // Check for normal data messages
+    else if( p2pMessage.isMsnObjectData()
+         ||  p2pMessage.isFileData())
     {
       gotDataFragment(p2pMessage);
     }
-    else if(waitingState_ == P2P_WAIT_FOR_FILE_DATA
-         && p2pMessage.getFlags() == 0
-         && p2pMessage.isFragment())
+    else if( waitingState_ == P2P_WAIT_FOR_FILE_DATA
+         &&  p2pMessage.getFlags() == 0
+         &&  p2pMessage.isFragment())
     {
       // HACK: added for Kopete 0.9.2 code (has no flag set with data messages).
       kWarning() << "Expecting data message, "
@@ -882,6 +882,16 @@
                     " class="   << metaObject()->className() << ")!";
       gotDataFragment(p2pMessage);
     }
+    else if( p2pMessage.getFlags() == 0
+         &&  p2pMessage.getDataSize() >= 18
+         &&  (quint8) ( p2pMessage.getData()[0] ) == 0x80 )
+    {
+      // Webcam setup. Even WLM sends then without flags.
+      // At the switchboard the "footercode" is set to 4.
+      // Pass it to the WebcamTransferP2P class to deal with it.
+      // Not using gotDataFragment() here because it will check if everything is received.
+      gotData( p2pMessage );
+    }
     else
     {
       // Unknown p2p message
@@ -1187,7 +1197,7 @@
   // Reserve the next message ID.
   nextMessageID_++;
 
-  // Set the total size field, this causes sendP2PMessage()
+  // Set the total size field, this causes sendP2PMessageImpl()
   // to handle fragmented messages correctly.
   // The 'fragmentMessageID_' is set so KMess sends all fragments with the same message ID.
   // It could happen the client receives a SLP ACK meanwhile, and ACKs it with a new message ID.
@@ -1220,7 +1230,7 @@
 
   // TODO: Check the various footer codes send for certain transfers.
   QByteArray p2pMessage( 4, 0x00 );
-  sendP2PMessage(p2pMessage, 0, 1, P2P_MSG_DATA_PREPARATION);
+  sendP2PMessageImpl( p2pMessage, 0, P2P_TYPE_PICTURE, P2P_MSG_DATA_PREPARATION );
 
   // TODO: after the data preparation is sent, WLM8 starts a direct connection setup:
   // - it does not send an ACK yet
@@ -1373,23 +1383,17 @@
               << " buffer=" << bytesRead;
 #endif
 
-    // Determine the progress value now because mesageOffset_ it reset by the last sendP2PMessage() call.
+    // Determine the progress value now because mesageOffset_ it reset by the last sendP2PMessageImpl() call.
     unsigned long nextProgressValue = ( fragmentOffset_ + bytesRead );
 
     // Determine the flags:
     // Funny, MSN6 doesn't seam to care which flag was set
     // while transferring the file. KMess however, does.
-    bool writeSuccess = false;
+    uint flag = 0;
     switch( dataType_ )
     {
-      case P2P_TYPE_PICTURE:
-        writeSuccess = sendP2PMessage( data, P2PMessage::MSN_FLAG_OBJECT_DATA, 1, P2P_MSG_DATA, fragmentMessageID_ );
-        break;
-
-      case P2P_TYPE_FILE:
-        writeSuccess = sendP2PMessage( data, P2PMessage::MSN_FLAG_FILE_DATA, 2, P2P_MSG_DATA, fragmentMessageID_ );
-        break;
-
+      case P2P_TYPE_PICTURE: flag = P2PMessage::MSN_FLAG_OBJECT_DATA; break;
+      case P2P_TYPE_FILE:    flag = P2PMessage::MSN_FLAG_FILE_DATA;   break;
       default:
         // Display error message for the first message only.
         if( fragmentOffset_ == 0 )
@@ -1401,10 +1405,11 @@
                         " class="   << metaObject()->className() <<
                         " action=tryflag).";
         }
-
-        writeSuccess = sendP2PMessage( data, 0, 1, P2P_MSG_DATA, fragmentMessageID_ ); // The best solution in this situation
     }
 
+    // Send the message
+    bool writeSuccess = sendP2PMessageImpl( data, flag, dataType_, P2P_MSG_DATA, fragmentMessageID_ );
+
     // Avoid next round if write was blocked already.
     // This could happen if a direct connection can't write all data.
     // The DirectConnectionBase class will automatically send the remaining parts, so don't worry about that here.
@@ -1524,7 +1529,7 @@
 
 
 /**
- * @brief Send a P2P ACK message.
+ * @brief Internal function to send a P2P ACK message.
  *
  * This method is the internal implementation for sendP2PAck() and others.
  *
@@ -1693,8 +1698,54 @@
 
 
 /**
- * @brief Sends a P2P Message.
+ * @brief Sends a complete P2P message payload.
  *
+ * It splits the data message into chunks which fit in the individual P2P messages.
+ *
+ * If you need to send data, use sendData() instead.
+ * This method is stream based, and also takes care of state changes.
+ *
+ * @param messageData    The message payload to send.
+ * @param flagField      The value for the flag field in the P2P header.
+ * @param footerCode     The message footer, which is appended when the message is sent over the switchboard.
+ * @param messageType    The type of the message. This value is returned with gotAck().
+ */
+void P2PApplicationBase::sendP2PMessage(const QByteArray &messageData, int flagField, P2PDataType footerCode, P2PMessageType messageType)
+{
+  // Make sure the message gets sent in chunks of 1202 bytes.
+  // Only set fragmentTotalSize_ if there are chunks, for useless debug messages.
+  int remainingBytes = messageData.size();
+  fragmentOffset_    = 0;
+  fragmentTotalSize_ = ( remainingBytes <= 1202 ? 0 : remainingBytes );
+
+  do
+  {
+    // Let another QByteArray encapsulate a part of the utf8Message
+    const char *dataPointer = messageData.data() + fragmentOffset_;
+    uint        dataSize    = qMin( remainingBytes, 1202 );
+    QByteArray  messagePart = QByteArray::fromRawData( dataPointer, dataSize );
+
+    // Send the message with the generic sendP2PMessageImpl() method
+    // fragmentOffset_ is updated automatically
+    sendP2PMessageImpl( messagePart, flagField, footerCode, messageType );
+    remainingBytes -= dataSize;
+
+#ifdef KMESSTEST
+    KMESS_ASSERT( (uint) remainingBytes == fragmentTotalSize_ - fragmentOffset_ );
+#endif
+  }
+  while( remainingBytes > 0 );
+
+  // Reset for normal operations
+  fragmentOffset_    = 0;
+  fragmentTotalSize_ = 0;
+}
+
+
+
+/**
+ * @brief Internal function to send a single P2P message.
+ *
  * This method constructs the P2P binary header fields.
  * The constructed message is delivered to ApplicationList::sendMessage().
  *
@@ -1705,18 +1756,20 @@
  * - outgoingMessages_ is updated to trace ACKs back later.
  *
  * The fragmentTotalSize_ should be set to the total size before sending message fragments.
- * This is done automatically by sendSlpMessage().
+ * This is done automatically by sendP2PMessage() function, which is part of the public API.
  *
  * @param messageData    The message payload to send. This can be binary file data or a SLP MIME data.
  * @param flagField      The value for the flag field in the P2P header.
  * @param footerCode     The message footer, which is appended when the message is sent over the switchboard.
  * @param messageType    The type of the message. This value is stored with in the unacked message queue.
  *                       This value is used later to respond to ACKs of certain special messages.
- * @param messageID      Optional message ID to enforce. If nothing is selected, the next available message ID will be used automatically.
+ * @param messageID      Optional message ID to enforce. If nothing is selected,
+ *                       the next available message ID will be used automatically.
+ *                       This parameter is used to send file data with a specific message ID.
  * @return               Whether the message can be sent. If the socket would block, false is returned.
  *                       This only occurs for direct connections connections.
  */
-bool P2PApplicationBase::sendP2PMessage(const QByteArray &messageData, int flagField, uint footerCode, P2PMessageType messageType, unsigned long messageID)
+bool P2PApplicationBase::sendP2PMessageImpl(const QByteArray &messageData, int flagField, P2PDataType footerCode, P2PMessageType messageType, unsigned long messageID)
 {
 #ifdef KMESSTEST
   KMESS_ASSERT( messageData.size() <= 1202 );
@@ -1762,6 +1815,7 @@
 
 
   // Determine the message size:
+  // Internal trick: fragmentTotalSize_ is set by the caller if this is a splitted message.
   uint messageSize = messageData.size();
   ulong totalSize;
   ulong offsetField;
@@ -1786,7 +1840,8 @@
   // Set session ID to zero when SLP messages are sent.
   unsigned long sessionID = getSessionID();
   if( flagField == 0
-  &&  messageType != P2P_MSG_DATA_PREPARATION )
+  &&  messageType != P2P_MSG_DATA_PREPARATION
+  &&  messageType != P2P_MSG_WEBCAM_SETUP )  // HACK!  to fix this we need to pass messages as 'P2PMessage'.
   {
     sessionID = 0;
   }
@@ -1804,7 +1859,7 @@
 
 
   // Deliver the message over the correct link (switchboard or direct connection)
-  bool writeSuccess = applicationList_->sendMessage( this, header, messageData, footerCode );
+  bool writeSuccess = applicationList_->sendMessage( this, header, messageData, (uint) footerCode );
   if( ! writeSuccess )
   {
     // This only happens when a direct connection transfer is congested.
@@ -1896,7 +1951,7 @@
  */
 void P2PApplicationBase::sendP2PWaitingError()
 {
-  sendP2PMessage(0, P2PMessage::MSN_FLAG_WAITING, 0);
+  sendP2PMessageImpl( 0, P2PMessage::MSN_FLAG_WAITING );
   shouldSendAck_ = false;
 }
 
@@ -1905,15 +1960,14 @@
 /**
  * @brief Send a complete SLP message in multiple P2P packets.
  *
- * It splits the SLP message into chunks which fit in the P2P payload.
- * Each part will be sent with sendP2PMessage().
- * The messages always have a Session ID of zero.
- *
  * This is a low-level function which is primary used by 
  * P2PApplication::sendSlpInvitation(), P2PApplication::sendSlpBye(), etc..
  *
+ * It converts the SLP string to the payload data,
+ * which could be splitted across multiple P2P messages.
+ *
  * @param  slpMessage   The whole SLP message to send, including SLP headers.
- * @param  messageType  The type of the message, used to trace ACK messages back later.
+ * @param  messageType  The type of the message. This value is returned with gotAck().
  */
 void P2PApplicationBase::sendSlpMessage(const QString &slpMessage, P2PMessageType messageType)
 {
@@ -1928,33 +1982,7 @@
   // Make sure the extra padded '\0' character is there. MSN 6-WLM rely on this.
   utf8Message.append('\0');
 
-  // Make sure the message gets sent in chunks of 1202 bytes.
-  // Only set fragmentTotalSize_ if there are chunks, for useless debug messages.
-  int remainingBytes = utf8Message.size();
-  fragmentOffset_    = 0;
-  fragmentTotalSize_ = ( remainingBytes <= 1202 ? 0 : remainingBytes );
-
-  do
-  {
-    // Let another QByteArray encapsulate a part of the utf8Message
-    char *dataPointer = utf8Message.data() + fragmentOffset_;
-    uint  dataSize    = qMin(remainingBytes, 1202);
-    messagePart = QByteArray::fromRawData( dataPointer, dataSize );
-
-    // Send the message with the generic sendP2PMessage() method
-    // fragmentOffset_ is updated automatically
-    sendP2PMessage(messagePart, 0, 0, messageType);
-    remainingBytes -= dataSize;
-
-#ifdef KMESSTEST
-    KMESS_ASSERT( (uint) remainingBytes == fragmentTotalSize_ - fragmentOffset_ );
-#endif
-  }
-  while(remainingBytes > 0);
-
-  // Reset for normal operations
-  fragmentOffset_    = 0;
-  fragmentTotalSize_ = 0;
+  sendP2PMessage( utf8Message, 0, P2P_TYPE_NEGOTIATION, messageType );  // will split the message.
 }
 
 
@@ -2137,7 +2165,7 @@
       }
 
       // Send the message
-      sendP2PMessage(0, waitFlag, 0);  // TODO: pass the original message which is unacked!!
+      sendP2PMessageImpl(0, waitFlag, 0);  // TODO: pass the original message which is unacked!!
 */
       testUnAckedMessages( true );  // TODO: argument is not used, and replaces sendP2PWaitingError().
 
Index: network/applications/webapplicationp2p.cpp
===================================================================
--- network/applications/webapplicationp2p.cpp	(revision 3566)
+++ network/applications/webapplicationp2p.cpp	(working copy)
@@ -82,12 +82,10 @@
 //  int realAppID    = appID.mid(4, 4).toInt();
 
   // Parse the context
-  context += "===="; // Make sure the base64 encoded string is null padded to avoid problems with QString::fromUtf16().
+  // Contents is something like '10331021;1;Tic Tac Toe'
   QByteArray decodedContext = QByteArray::fromBase64( context.toLatin1() );
+  QString    contextString  = QString::fromUtf16( reinterpret_cast<const ushort*>( decodedContext.data() ), decodedContext.size() / 2 );
 
-  // Contents is something like '10331021;1;Tic Tac Toe'
-  QString contextString = QString::fromUtf16( reinterpret_cast<const ushort*>( decodedContext.data() ), decodedContext.size() / 2 );
-
   // Get the fields
   QString unknown = contextString.section(';', 1, 1);
   QString appName = contextString.section(';', 2, 2);
Index: network/applications/p2papplication.cpp
===================================================================
--- network/applications/p2papplication.cpp	(revision 3566)
+++ network/applications/p2papplication.cpp	(working copy)
@@ -2787,9 +2787,8 @@
 
 #ifdef KMESSTEST
   // Test, as other invocations likely result in undefined client behavour
-  // (
   KMESS_ASSERT(   gotSlpMessage_ );
-  KMESS_ASSERT( isWaitingState( P2P_WAIT_DEFAULT ) );
+  KMESS_ASSERT( isWaitingState( P2P_WAIT_DEFAULT ) || isWaitingState( P2P_WAIT_USER_ACCEPT ) );
   KMESS_ASSERT( ! callID_.isEmpty() );
   KMESS_ASSERT( ! branch_.isEmpty() );
 #endif
Index: network/applications/p2papplicationbase.h
===================================================================
--- network/applications/p2papplicationbase.h	(revision 3566)
+++ network/applications/p2papplicationbase.h	(working copy)
@@ -228,7 +228,8 @@
       P2P_MSG_DATA                = 5,  ///< The actual data message.
       P2P_MSG_SESSION_BYE         = 6,  ///< The SLP BYE message.
       P2P_MSG_TRANSFER_DECLINE    = 7,  ///< The SLP 603 Decline message.
-      P2P_MSG_SLP_ERROR           = 8   ///< One of the possible SLP error messages.
+      P2P_MSG_SLP_ERROR           = 8,  ///< One of the possible SLP error messages.
+      P2P_MSG_WEBCAM_SETUP        = 8   ///< One of the possible webcam setup messages.
     };
 
     /**
@@ -242,6 +243,7 @@
     , P2P_TYPE_PICTURE     = 1  ///< Packet contains MsnObject data.
     , P2P_TYPE_FILE        = 2  ///< Packet contains file data.
     , P2P_TYPE_INK         = 3  ///< Packet contains an Ink message.
+    , P2P_TYPE_WEBCAM      = 4  ///< Packet contains a webcam setup message.
     };
 
     /**
@@ -260,6 +262,7 @@
       P2P_WAIT_DEFAULT             = 0,  ///< Not waiting at all.
       P2P_WAIT_FOR_SLP_OK_ACK      = 1,  ///< Waiting for remote client to ack the the SLP OK message.
       P2P_WAIT_FOR_FILE_DATA       = 2,  ///< Waiting for remote client to send file data.
+      P2P_WAIT_FOR_WEBCAM_DATA     = 19, ///< Waiting for remote client to send webcam invite data.
       P2P_WAIT_FOR_PREPARE         = 3,  ///< Waiting for remote client to send some prepare message.
       P2P_WAIT_FOR_PREPARE_ACK     = 4,  ///< Waiting for remote client to ack the data preparation.
       P2P_WAIT_FOR_SLP_BYE         = 5,  ///< Waiting for remote client to send the SLP BYE.
@@ -332,6 +335,8 @@
     void                   sendP2PAbort();
     // Send an low-level ACK message for a received message if this is needed.
     bool                   sendP2PAck();
+    // Send a low-level P2P data message
+    void                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, P2PDataType footerCode = P2P_TYPE_NEGOTIATION, P2PMessageType messageType = P2P_MSG_UNKNOWN);
     // Send a low-level error message that the application is waiting for a certain message.
     void                   sendP2PWaitingError();
     // Send a given string using sendP2PMessage()
@@ -384,7 +389,7 @@
     // Send a P2P ACK message
     void                   sendP2PAckImpl(int ackType = P2PMessage::MSN_FLAG_ACK, UnAckedMessage *originalMessageData = 0);
     // Send a P2P message
-    bool                   sendP2PMessage(const QByteArray &messageData, int flagField = 0, uint footerCode = 0, P2PMessageType messageType = P2P_MSG_UNKNOWN, unsigned long messageID = 0);
+    bool                   sendP2PMessageImpl(const QByteArray &messageData, int flagField = 0, P2PDataType footerCode = P2P_TYPE_NEGOTIATION, P2PMessageType messageType = P2P_MSG_UNKNOWN, unsigned long messageID = 0);
     // Test if there are still unacked messages.
     void                   testUnAckedMessages(bool sendError);
 
Index: network/applications/webcamtransferp2p.cpp
===================================================================
--- network/applications/webcamtransferp2p.cpp	(revision 3566)
+++ network/applications/webcamtransferp2p.cpp	(working copy)
@@ -18,12 +18,22 @@
 #include "webcamtransferp2p.h"
 
 #include "../../kmessdebug.h"
+#include "../../utils/kmessshared.h"
 #include "../mimemessage.h"
 
 #include <KLocale>
+#include <stdlib.h>   // rand()
 
 
+//
+// I'd like to have many thanks to the Kopete developers here,
+// without them writing this code would have been a lot harder.
+// Especially since all webcam online docs seam to be gone.
+//
 
+
+
+
 /**
  * Constructor
  *
@@ -31,6 +41,7 @@
  */
 WebcamTransferP2P::WebcamTransferP2P(ApplicationList *applicationList)
 : P2PApplication(applicationList)
+, isUserSender_(false)
 {
   setApplicationType( ChatMessage::TYPE_APPLICATION_WEBCAM );
 }
@@ -63,42 +74,148 @@
  *
  * @param  message  The invitation message
  */
-void WebcamTransferP2P::contactStarted1_ContactInvitesUser(const MimeMessage & /*message*/)
+void WebcamTransferP2P::contactStarted1_ContactInvitesUser(const MimeMessage & message)
 {
-#ifdef KMESSDEBUG_WEBAPPLICATION_P2P
+#ifdef KMESSDEBUG_WEBCAMTRANSFER_P2P
   kDebug();
 #endif
 
-#if 0
-  QString  appID   = message.getValue("AppID");
-  QString  eufGuid = message.getValue("EUF-GUID");
-  QString  context = message.getValue("Context");
+  // Read the values from the message
+  uint    appID   = message.getValue("AppID").toUInt();
+  QString eufGuid = message.getValue("EUF-GUID");
+  QString context = message.getValue("Context");
 
-  // Parse the context
-  context += "===="; // Make sure the base64 encoded string is null padded to avoid problems with QString::fromUtf16().
-  QByteArray decodedContext;
-  KCodecs::base64Decode(context.utf8(), decodedContext);
+  if(appID != 4)
+  {
+    kWarning() << "Received unexpected AppID: " << appID << ".";
 
-  // Contents is another GUID
-  QString contextString = QString::fromUtf16( reinterpret_cast<const unsigned short*>(decodedContext.data()) );
+    // Wouldn't know what to do if the AppID is not 2, so send an 500 Internal Error back.
+    showEventMessage( i18n("The webcam invitation was cancelled. Bad data was received."), ChatMessage::CONTENT_APP_CANCELED, true );
+    sendCancelMessage( CANCEL_ABORT );
+    return;
+  }
+
+  // Contact can request a webcam from us, or share it's own webcam.
+  isUserSender_ = ( eufGuid == getPullAppId() );
+
+  // Parse the context, it contains another GUI
+  QByteArray decodedContext = QByteArray::fromBase64( context.toLatin1() );
+  webcamGuid_ = P2PMessage::extractUtf16String( decodedContext.data(), 0, decodedContext.size() );
+
+  // Display the accept message.
+#ifdef KMESSDEBUG_WEBCAMTRANSFER_P2P
+  kDebug() << "Webcam invitation received, GUID=" << webcamGuid_ << endl;
+  kDebug() << "waiting for user to accept..." << endl;
 #endif
 
-  // KMess does not support the webapplication invitations yet,
-  // this class is currently a STUB to produce a proper error message
-  showSystemMessage( i18n( "The contact is inviting you for '%1', but this is not implemented yet.",
-                           i18n("webcam") ),
-                     ChatMessage::CONTENT_SYSTEM_NOTICE, true );
+  offerAcceptOrReject( i18n("You are invited to view this person's webcam.") );
+}
 
-  // Tell the contact we don't support this.
-  sendCancelMessage(CANCEL_NOT_INSTALLED);
 
-//  // Everything seams OK, accept this message:
-//  contactStarted2_UserAccepts();
+
+/**
+ * Step two of a contact-started chat: the user accepts
+ */
+void WebcamTransferP2P::contactStarted2_UserAccepts()
+{
+#ifdef KMESSDEBUG_FILETRANSFER_P2P
+  kDebug() << "sending accept message";
+#endif
+
+  // Create the message
+  MimeMessage message;
+  message.addField( "SessionID", QString::number( getInvitationSessionID() ) );
+
+  // Send the message
+  sendSlpOkMessage(message);
+  setWaitingState( P2P_WAIT_FOR_WEBCAM_DATA, 120000 );  // 120 seconds
 }
 
 
 
+
 /**
+ * Step three of a contact-started chat: the contact confirms the accept
+ */
+void WebcamTransferP2P::contactStarted3_ContactConfirmsAccept(const MimeMessage& /*message*/)
+{
+#ifdef KMESSDEBUG_WEBCAMTRANSFER_P2P
+  kDebug();
+#endif
+
+  // The invitation continues with the gotData() call.
+}
+
+
+
+/**
+ * Create the producer or viewer XML tag.
+ */
+QString WebcamTransferP2P::createSipXml( uint session, uint rid )
+{
+  QString rootNode = ( isUserSender_ ? "producer" : "viewer" );
+
+  QString ipTags = "<tcpipaddress1>10.0.0.1</tcpipaddress1>";
+
+  /*
+  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) ;
+  */
+
+  int port = 0;
+
+  return "<" + rootNode + ">"
+           "<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>7786</tcpport>"     // "\t\t\t\t\t\t\t\t  "
+             "<tcplocalport>7786</tcplocalport>"  // "\t\t\t\t\t\t\t\t  "
+             "<tcpexternalport>7786</tcpexternalport>" + ipTags +
+           "</tcp>"
+           "<udp>"
+             "<udplocalport>7786</udplocalport>"
+             "<udpexternalport>31863</udpexternalport>"
+             "<udpexternalip>" + ipTags + "</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>"
+         "</" + rootNode + ">\r\n\r\n";
+}
+
+
+
+/**
+ * Return the application's GUID for "video conference" invitations.
+ */
+QString WebcamTransferP2P::getConferenceAppId()
+{
+  return "{4BD96FC0-AB17-4425-A14A-439185962DC8}";
+}
+
+
+
+/**
  * Return the application's GUID for "user push" invitations.
  */
 QString WebcamTransferP2P::getPushAppId()
@@ -117,4 +234,133 @@
 }
 
 
+
+/**
+ * Called when SIP data is received.
+ * This is the second stage of the webcam invitation.
+ *
+ * @param  message  P2P message with the data.
+ */
+void WebcamTransferP2P::gotData(const P2PMessage &message)
+{
+  bool contactStarted = ( ! isUserStartedApp() );
+  QString contents = P2PMessage::extractUtf16String( message.getData(), 10, message.getDataSize() - 10 );
+
+#ifdef KMESSDEBUG_FILETRANSFER_P2P
+  kDebug() << "received setup data:" << contents;
+#endif
+
+  sendP2PAck();
+
+  // Find out what to do.
+  if( contents == "syn" )
+  {
+    if( contactStarted )
+    {
+      sendSipMessage("syn", 0x17,0x2a,0x01);
+    }
+    else
+    {
+      sendSipMessage("ack", 0xea,0x00,0x00);
+    }
+  }
+  else if( contents == "ack" )
+  {
+    if( contactStarted )
+    {
+      sendSipMessage("ack", 0xea,0x00,0x00);
+    }
+
+    // Send the <producer> message
+    if( isUserSender_ )
+    {
+      uint session    = rand() % 1000 + 5000;
+      uint rid        = rand() % 100 + 50;
+      authentication_ = "recipientid=" + QString::number( rid ) + "&sessionid=" + QString::number( session ) + "\r\n\r\n";
+      QString producerXml = createSipXml( session, rid );
+      sendSipMessage( producerXml, 0, 0, 0 );
+    }
+  }
+  else if( contents.startsWith("<") )
+  {
+    // XML.
+  }
+  else
+  {
+    sendP2PAbort();
+  }
+}
+
+
+
+/**
+ * Send a webcam negotiation message
+ */
+void WebcamTransferP2P::sendSipMessage( const QString &contents, quint8 flag1, quint8 flag2, quint8 flag3 )
+{
+  QByteArray sipMessage(10 + contents.length() * 2 + 2, '\0');
+
+  sipMessage[0] = 0x80;
+  sipMessage[1] = flag1;
+  sipMessage[2] = flag2;
+  sipMessage[3] = flag3;
+  sipMessage[4] = 0x08;
+
+  P2PMessage::insertUtf16String( sipMessage, contents, 10 );
+
+  // Send message.
+  sendP2PMessage( sipMessage, 0, P2P_TYPE_WEBCAM, P2P_MSG_WEBCAM_SETUP );
+}
+
+
+
+/**
+ * Step one of a user-started chat: the user invites the contact
+ */
+void WebcamTransferP2P::userStarted1_UserInvitesContact()
+{
+#ifdef KMESSDEBUG_FILETRANSFER_P2P
+  kDebug() << "starting webcam session";
+#endif
+
+  // Generate context field
+  webcamGuid_ = "{11CF1BE9-3186-93A5-B38D-914DB1163BF7}";
+
+  // Write the string
+  int contextLength = webcamGuid_.size() * 2 + 2;
+  QByteArray context( contextLength, '\0' );
+  P2PMessage::insertUtf16String( context, webcamGuid_, 0 );
+
+  // Encode to base64
+  QString encodedContext = context.toBase64().replace("AAAA", "AA==");  // TODO: find out why the null padding is wrong.
+
+  encodedContext = "ewAwADQAMgA1AEUANwA5ADcALQA0ADkARgAxAC0ANABEADMANwAtADkAMAA5AEEALQAwADMAMQAxADEANgAxADEAOQBEADkAQgB9AA==";
+
+  // Send the invitation
+  sendSlpSessionInvitation( KMessShared::generateID(), getPushAppId(), 4, encodedContext );
+}
+
+
+
+/**
+ * Step two of a user-started chat: the contact accepts
+ */
+void WebcamTransferP2P::userStarted2_ContactAccepts(const MimeMessage &message)
+{
+
+}
+
+
+
+/**
+ * Step three of a user-started chat: the user prepares for the session
+ */
+void WebcamTransferP2P::userStarted3_UserPrepares()
+{
+
+
+}
+
+
+
 #include "webcamtransferp2p.moc"
Index: network/applications/webcamtransferp2p.h
===================================================================
--- network/applications/webcamtransferp2p.h	(revision 3566)
+++ network/applications/webcamtransferp2p.h	(working copy)
@@ -44,6 +44,9 @@
     // The destructor
     virtual               ~WebcamTransferP2P();
 
+    // Return the application's GUID for "video conference" invitations.
+    static QString         getConferenceAppId();
+
     // Return the application's GUID (for the "user push" invitation)
     static QString         getPushAppId();
 
@@ -54,6 +57,31 @@
 
     // Step one of a contact-started chat: the contact invites the user
     void                   contactStarted1_ContactInvitesUser(const MimeMessage& message);
+    // Step two of a contact-started chat: the user accepts
+    void                   contactStarted2_UserAccepts();
+    // Step three of a contact-started chat: the contact confirms the accept
+    void                   contactStarted3_ContactConfirmsAccept(const MimeMessage& message);
+    // Create the producer or viewer XML tag
+    QString                createSipXml(uint session, uint rid);
+    // Called when data is received
+    void                   gotData(const P2PMessage &message);
+    // Send a webcam negotiation message.
+    void                   sendSipMessage( const QString &contents, quint8 flag1, quint8 flag2, quint8 flag3 );
+    // Step one of a user-started chat: the user invites the contact
+    void                   userStarted1_UserInvitesContact();
+    // Step two of a user-started chat: the contact accepts
+    void                   userStarted2_ContactAccepts(const MimeMessage &message);
+    // Step three of a user-started chat: the user prepares for the session
+    void                   userStarted3_UserPrepares();
+
+
+  private:
+    // The authentication string for the webcam connection
+    QString                authentication_;
+    // Whether the contact requests the webcam from us, or it wants to share it's webcam.
+    bool                   isUserSender_;
+    // The GUID received in the invitation
+    QString                webcamGuid_;
 };
 
 #endif
Index: network/p2pmessage.cpp
===================================================================
--- network/p2pmessage.cpp	(revision 3566)
+++ network/p2pmessage.cpp	(working copy)
@@ -103,6 +103,7 @@
   return ackDataSize_;
 }
 
+
 // Retreives the nonce field (for direct connection handshake.
 QString P2PMessage::getNonce() const
 {
@@ -258,6 +259,16 @@
   ackSessionID_    = extractBytes     ( messageData, 32);
   ackUniqueID_     = extractBytes     ( messageData, 36);
   ackDataSize_     = extractLongBytes ( messageData, 40);
+
+  // Verify data size!
+  if( (uint) message_.size() < ( 48 + dataSize_ ) )  // message size could also contain the footer code.
+  {
+    kWarning().nospace() << "corrupt data size header detected "
+                            "(header=48"
+                            " datasize=" << dataSize_ <<
+                            " total=" << message_.size() << ")";
+    dataSize_ = message_.size() - 48;
+  }
 }
 
 
@@ -267,6 +278,8 @@
 // ------------------------------------------------------
 // A utility to fill the byte data block
 
+
+
 void P2PMessage::insertBytes(QByteArray &buffer, const unsigned int value, const int offset)
 {
   // Also based on Kopete code. I started with a nice struct,
@@ -294,12 +307,8 @@
   // However, note the bytes are placed in network order. (big endian)
 }
 
-void P2PMessage::insertShortBytes(QByteArray &buffer, const unsigned short value, const int offset)
-{
-  buffer[offset + 0] = (char) ( value        & 0xFF);
-  buffer[offset + 1] = (char) ((value >>  8) & 0xFF);
-}
 
+
 void P2PMessage::insertNonce(QByteArray &buffer, const QString &nonce, const int offset)
 {
   // Remove the separators
@@ -327,6 +336,24 @@
   }
 }
 
+
+
+// Copy sort integers into the binary header.
+void P2PMessage::insertUtf16String(QByteArray &buffer, const QString &value, int offset)
+{
+  const unsigned short *utf16Value = value.utf16();
+  uint valueLength = value.length();
+  for(uint i = 0; i < valueLength; ++i)
+  {
+    const short utf16Char = utf16Value[i];
+    buffer[offset + 0] = (char) ( utf16Char        & 0xFF);
+    buffer[offset + 1] = (char) ((utf16Char >>  8) & 0xFF);
+    offset += 2;
+  }
+}
+
+
+
 unsigned int P2PMessage::extractBytes(const char *data, const int offset)
 {
   // Convert the bytes from network order to a normal int.
@@ -336,6 +363,8 @@
         | ((unsigned char) data[offset + 3] << 24));
 }
 
+
+
 unsigned long P2PMessage::extractLongBytes(const char *data, const int offset)
 {
   // Convert the bytes from network order to a long int.
@@ -354,6 +383,7 @@
 }
 
 
+
 QString P2PMessage::extractNonce(const char *data, const int offset)
 {
   const int noncePos[] = { 3,2,1,0            // Field 1 reversed
@@ -380,3 +410,20 @@
   return "{" + hex + "}";
 }
 
+
+
+QString P2PMessage::extractUtf16String( const char *data, const int offset, int size )
+{
+  // Avoid copying the null char
+  if( data[ offset + size - 1 ] == '\0'
+  &&  data[ offset + size - 2 ] == '\0' )
+  {
+#ifdef KMESSDEBUG_P2PMESSAGE
+    kDebug() << "avoid copying null character to utf16 string";
+#endif
+    size--;
+  }
+
+  return QString::fromUtf16( reinterpret_cast<const ushort *>( data + offset ), size / 2 );
+}
+
Index: network/p2pmessage.h
===================================================================
--- network/p2pmessage.h	(revision 3566)
+++ network/p2pmessage.h	(working copy)
@@ -104,16 +104,20 @@
 
   // Copy integers into the binary header.
   static          void insertBytes(QByteArray& buffer, const unsigned int value, const int offset);
-  // Copy sort integers into the binary header.
+  // Copy short integers into the binary header.
   static          void insertShortBytes(QByteArray &buffer, const unsigned short value, const int offset);
   // Copy a nonce string into the binary header.
   static          void insertNonce(QByteArray& buffer, const QString &nonce, const int offset = 32);
+  // Copy short integers into the binary header.
+  static          void insertUtf16String(QByteArray &buffer, const QString &value, int offset);
   // Extracts the bytes from the data block.
   static unsigned int  extractBytes(const char *data, const int offset);
   // Extracts the bytes from the data block.
   static unsigned long extractLongBytes(const char *data, const int offset);
   // Extracts the nonce string from the data header.
   static QString       extractNonce(const char *data, const int offset = 32);
+  // Extracts an utf16 string from the data header.
+  static QString       extractUtf16String(const char *data, const int offset, int size);
 
 
 private:    // Helper functions
Index: network/msnnotificationconnection.cpp
===================================================================
--- network/msnnotificationconnection.cpp	(revision 3566)
+++ network/msnnotificationconnection.cpp	(working copy)
@@ -495,7 +495,7 @@
 #else
   // NOTE: When changing this property, all MSNP2P code need to be retested again!!
   // The capabilities are like a contract. The other client assumes everything works that's promised here.
-  const uint capabilities = ( Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET | Contact::MSN_CAP_INK_GIF );
+  const uint capabilities = ( Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET | Contact::MSN_CAP_INK_GIF | Contact::MSN_CAP_VIDEO_CHAT );
 #endif
 
   QString statusCode = MsnStatus::getCode( newStatus );
Index: chat/chatmaster.h
===================================================================
--- chat/chatmaster.h	(revision 3566)
+++ chat/chatmaster.h	(working copy)
@@ -125,6 +125,8 @@
     void               slotSwitchboardReady();
     // Start a netmeeting invitation
     void               startNetMeeting(const QString &handle);
+    // Start a webcam transfer with the contact
+    void               startWebcamTransfer( const QString &handle );
 
   private:  // private methods
     // Create and register a new switchboard
Index: chat/chatwindow.cpp
===================================================================
--- chat/chatwindow.cpp	(revision 3566)
+++ chat/chatwindow.cpp	(working copy)
@@ -493,12 +493,13 @@
 
   // Create the actions
   sendAction_        = new KAction( KIcon("folder-remote"),                         i18n("Send a &File"),     this );
+  webcamAction_      = new KAction( KIcon("webcamsend"),                            i18n("Webcam chat"),     this );
   meetingAction_     = new KAction( KIcon("gnomemeeting"),                          i18n("Start a &Meeting"), this );
   nudgeAction_       = new KAction( KIcon("preferences-desktop-notification-bell"), i18n("Send a &Nudge!"),   this );
   saveAction         = new KAction( KIcon("document-save"),                         i18n("Save chat"),        this );
   addEmoticonAction_ = new KAction( KIcon("emoticons"),                             i18n("&Emoticons"),       this );
+  closeAllAction_    = new KAction( KIcon("dialog-close"),                          i18n("Close &All Tabs"),  this );
   closeAction        = KStandardAction::close( this, SLOT( queryClose() ), actionCollection_ );
-  closeAllAction_    = new KAction( KIcon("dialog-close"),                          i18n("Close &All Tabs"),  this );
 
   // Initially the chat has one tab only, so the Close All option is not useful
   closeAllAction_->setVisible( false );
@@ -512,18 +513,13 @@
   inviteButton_->setDelayed( false );
 
   // Connect them
-  connect( sendAction_,        SIGNAL(         triggered(bool) ),
-           this,               SLOT  ( startFileTransfer()     ) );
-  connect( nudgeAction_,       SIGNAL(         triggered(bool) ),
-           this,               SLOT  (         sendNudge()     ) );
-  connect( meetingAction_,     SIGNAL(         triggered(bool) ),
-           this,               SLOT  (      startMeeting()     ) );
-  connect( saveAction,         SIGNAL(         triggered(bool) ),
-           this,               SLOT  (          saveChat()     ) );
-  connect( addEmoticonAction_, SIGNAL(         triggered()     ),
-           this,               SLOT  ( showEmoticonPanel()     ) );
-  connect( closeAllAction_,    SIGNAL(         triggered()     ),
-           this,               SLOT  (     queryCloseAll()     ) );
+  connect( sendAction_,        SIGNAL( triggered(bool) ), this, SLOT(   startFileTransfer() ) );
+  connect( webcamAction_,      SIGNAL( triggered(bool) ), this, SLOT( startWebcamTransfer() ) );
+  connect( nudgeAction_,       SIGNAL( triggered(bool) ), this, SLOT(           sendNudge() ) );
+  connect( meetingAction_,     SIGNAL( triggered(bool) ), this, SLOT(        startMeeting() ) );
+  connect( saveAction,         SIGNAL( triggered(bool) ), this, SLOT(            saveChat() ) );
+  connect( addEmoticonAction_, SIGNAL( triggered()     ), this, SLOT(   showEmoticonPanel() ) );
+  connect( closeAllAction_,    SIGNAL( triggered()     ), this, SLOT(       queryCloseAll() ) );
 
 #ifdef HAS_KPHONE  // If KPhone is not enabled, don't support voice conversation
   KAction *conversation = new KAction( "kphone", i18n("Start or Stop a &Conversation"), this );
@@ -534,7 +530,8 @@
   // The Invite menu goes before this separator
   firstChatMenuItem_ = chatMenu_->addSeparator();
   chatMenu_->addAction( sendAction_        );
-  chatMenu_->addAction( meetingAction_     );
+  chatMenu_->addAction( webcamAction_      );
+  chatMenu_->addAction( meetingAction_     );   // TODO: remove this.
 #ifdef HAS_KPHONE  // If KPhone is not enabled, don't support voice conversation
   chatMenu_->addAction( conversation       );
 #endif  
@@ -548,6 +545,7 @@
   // removed 'close', not a frequently used action (and you'll have Alt+F4 and the window close button for that).
   toolBar()->addAction( inviteButton_      );
   toolBar()->addAction( sendAction_        );
+  toolBar()->addAction( webcamAction_      );
   toolBar()->addAction( nudgeAction_       );
   toolBar()->addAction( addEmoticonAction_ );
 
@@ -569,11 +567,9 @@
 
   // Create the "edit" menus
   changeFont      = new KAction( KIcon("preferences-desktop-font"), i18n("Change &Font"), this );
-  changeFontColor = new KAction( KIcon("format-stroke-color"), i18n("Change Font &Color"), this );
-  connect( changeFont,      SIGNAL(     triggered(bool) ),
-           this,              SLOT(      editFont()     ) );
-  connect( changeFontColor, SIGNAL(     triggered(bool) ),
-           this,              SLOT( editFontColor()     ) );
+  changeFontColor = new KAction( KIcon("format-stroke-color"),      i18n("Change Font &Color"), this );
+  connect( changeFont,      SIGNAL( triggered(bool) ), this, SLOT(      editFont() ) );
+  connect( changeFontColor, SIGNAL( triggered(bool) ), this, SLOT( editFontColor() ) );
 
   // Add shorter texts for the toolbar
   changeFont ->setIconText( i18n("&Font") );
@@ -1467,6 +1463,13 @@
 
 
 
+// Start a file transfer
+void ChatWindow::startWebcamTransfer()
+{
+  getCurrentChat()->startWebcamTransfer();
+}
+
+
 // Start a voice conversation
 void ChatWindow::startConversation()
 {
Index: chat/chatwindow.h
===================================================================
--- chat/chatwindow.h	(revision 3566)
+++ chat/chatwindow.h	(working copy)
@@ -180,6 +180,8 @@
     void             startFileTransfer();
     // Start GnomeMeeting with a contact.
     void             startMeeting();
+    // Start a webcam transfer
+    void             startWebcamTransfer();
     // Called when the "use emoticons" action is called.
     void             toggleEmoticons( bool useEmoticons );
     // Called when the "show sidebar" action is called.
@@ -188,6 +190,8 @@
     void             toggleSpellCheck( bool useSpellCheck );
 
   private: // private attributes
+    // ActionCollection
+    KActionCollection *actionCollection_;
     // The add emoticon action
     KAction         *addEmoticonAction_;
     // A timer used to make the caption blink on new messages.
@@ -250,8 +254,8 @@
     QTimer           statusTimer_;
     // The bar of chat tabs
     KTabBar         *tabBar_;
-    // ActionCollection
-    KActionCollection *actionCollection_;
+    // Action to start a file transfer
+    KAction         *webcamAction_;
 
   signals: // Public signals
     // Signal that the window is about to close.
Index: chat/chat.cpp
===================================================================
--- chat/chat.cpp	(revision 3566)
+++ chat/chat.cpp	(working copy)
@@ -1261,4 +1261,22 @@
 
 
 
+// Send webcam data to a contact.
+void Chat::startWebcamTransfer()
+{
+  // Choose the contact.
+  QString handle = chooseContact();
+
+  if( KMESS_NULL(msnSwitchboardConnection_) )
+  {
+#ifdef KMESSDEBUG_CHAT_GENERAL
+    kWarning() << "Tried to open a meeting with \'" << handle << "\' without a switchboard!";
+#endif
+    return;
+  }
+
+  emit requestWebcamTransfer(handle);
+}
+
+
 #include "chat.moc"
Index: chat/chat.h
===================================================================
--- chat/chat.h	(revision 3566)
+++ chat/chat.h	(working copy)
@@ -106,6 +106,8 @@
     void               startConversation();
     // Start GnomeMeeting with a contact.
     void               startMeeting();
+    // Send webcam data to a contact.
+    void               startWebcamTransfer();
 
   private: // Private methods
     // Check if the user enabled an auto-reply, and send it
@@ -166,6 +168,8 @@
     void               requestFileTransfer(const QString &handle, const QString &filename);
     // Signal that the ChatMaster should start a newmeeting session.
     void               requestNetMeeting(const QString &handle);
+    // Signal that the ChatMaster should start a webcam transfer.
+    void               requestWebcamTransfer(const QString &handle);
     // The user wants to add or remove a contact.
     void               setContactAdded( QString handle, bool isAdded );
     // The user wants to allow a contact to see his/hers online status.
Index: chat/chatmaster.cpp
===================================================================
--- chat/chatmaster.cpp	(revision 3566)
+++ chat/chatmaster.cpp	(working copy)
@@ -23,12 +23,13 @@
 #include "../contact/msnobject.h"
 #include "../network/applications/applicationlist.h"
 #include "../network/applications/mimeapplication.h"
-#include "../network/applications/msnobjecttransferp2p.h"
 #include "../network/applications/p2papplication.h"
 #include "../network/applications/filetransfer.h"
 #include "../network/applications/filetransferp2p.h"
 #include "../network/applications/gnomemeeting.h"
 #include "../network/applications/inktransferp2p.h"
+#include "../network/applications/msnobjecttransferp2p.h"
+#include "../network/applications/webcamtransferp2p.h"
 #include "../network/chatmessage.h"
 #include "../network/msnswitchboardconnection.h"
 #include "../currentaccount.h"
@@ -1473,6 +1474,8 @@
            this,    SLOT  (        startFileTransfer(const QString&,const QString&) ) );
   connect( newChat, SIGNAL(        requestNetMeeting(const QString&)                ),
            this,    SLOT  (          startNetMeeting(const QString&)                ) );
+  connect( newChat, SIGNAL(    requestWebcamTransfer(const QString&)                ),
+           this,    SLOT  (      startWebcamTransfer(const QString&)                ) );
   connect( newChat, SIGNAL(                  closing(QObject*)                      ),
            this,    SLOT  (          slotChatClosing(QObject*)                      ) );
   connect( newChat, SIGNAL(                destroyed(QObject*)                      ),
@@ -1651,12 +1654,6 @@
 
   if( contact != 0 && contact->hasP2PSupport() )
   {
-    // The contact supports file transfer over MSNP2P
-    P2PApplication *app = new FileTransferP2P(appList, filename);
-    startApplication(app);
-  }
-  else
-  {
     // This should be so rare that it justifies a warning message.
     if( contact == 0 )
     {
@@ -1670,7 +1667,12 @@
     // The contact only supports file transfer the old way
     MimeApplication *app = new FileTransfer(handle, filename);
     startApplication(app);
+    return;
   }
+
+  // The contact supports file transfer over MSNP2P
+  P2PApplication *app = new FileTransferP2P(appList, filename);
+  startApplication(app);
 }
 
 
@@ -1761,6 +1763,27 @@
 
 
 
+// Start a webcam transfer with the contact
+void ChatMaster::startWebcamTransfer( const QString &handle )
+{
+  ApplicationList *appList = getApplicationList(handle);
+  if(KMESS_NULL(appList)) return;
+
+  // Get the contact properties, see how we can transfer the file.
+  const ContactBase *contact = CurrentAccount::instance()->getContactByHandle( handle );
+  if( contact == 0 || ! contact->hasP2PSupport() )
+  {
+    kWarning() << "Contact" << handle << "does not support P2P transfers, using old MIME invitations instead.";
+    return;
+  }
+
+  // The contact supports file transfer over MSNP2P
+  P2PApplication *app = new WebcamTransferP2P(appList);
+  startApplication(app);
+}
+
+
+
 /**
  * Periodically called method for update commands.
  *
Index: kmessdebug.h
===================================================================
--- kmessdebug.h	(revision 3566)
+++ kmessdebug.h	(working copy)
@@ -87,6 +87,7 @@
 
   #define KMESSDEBUG_MIMEMESSAGE
   #define KMESSDEBUG_MULTIPACKETMESSAGE
+  #define KMESSDEBUG_P2PMESSAGE
   #define KMESSDEBUG_APPLICATION
   #define KMESSDEBUG_APPLICATIONLIST
   #define KMESSDEBUG_MIMEAPPLICATION
@@ -96,6 +97,7 @@
   #define KMESSDEBUG_FILETRANSFER
   #define KMESSDEBUG_REMOTEDESKTOP
   #define KMESSDEBUG_WEBAPPLICATION_P2P
+  #define KMESSDEBUG_WEBCAMTRANSFER_P2P
 
   #define KMESSDEBUG_UPNP
   #define KMESSDEBUG_SOAPMESSAGE
Index: dialogs/networkwindow.cpp
===================================================================
--- dialogs/networkwindow.cpp	(revision 3566)
+++ dialogs/networkwindow.cpp	(working copy)
@@ -17,8 +17,6 @@
 
 #include "../kmessdebug.h"
 
-#ifdef KMESS_NETWORK_WINDOW
-
 #include "networkwindow.h"
 
 #include "../network/mimemessage.h"
@@ -457,8 +455,10 @@
 
 
   // Return a log description for the message
-  QString NetworkWindow::describeP2PMessage(const QByteArray &message, const int headerStart)
+  QString NetworkWindow::describeP2PMessage(const QByteArray &message, const int headerStart, int footerCode)
   {
+    Q_UNUSED(footerCode);
+
     // Prepare to extract certain P2P header fields
     QDataStream binaryStream( message );
     binaryStream.setByteOrder( QDataStream::LittleEndian );
@@ -504,6 +504,15 @@
           }
         }
 
+        // Another awkward data message (webcam setup)
+        if( sessionId  != 0
+        &&  flags      == 0
+        &&  dataSize   >= 18
+        &&  (quint8) ( message[ headerStart + 48 ] ) == 0x80 )
+        {
+          return formatDescription("Webcam setup message");
+        }
+
         // No flags means the contents is a string with SLP-like mime fields, used to negotiate the session.
         // datasize of 0 is not handed here, is an invalid packet.
         // KMess does parse it as ACK message for compatibility with broken clients.
@@ -711,8 +720,8 @@
         else
         {
           // A p2p message from the direct connection
-          logMessage += formatP2PMessage(incoming, message, 0);
-          logMessage += describeP2PMessage(message, 0);
+          logMessage += formatP2PMessage(incoming, message, 0, 0);
+          logMessage += describeP2PMessage(message, 0, 0);
         }
         break;
 
@@ -759,11 +768,12 @@
             // Extract the first utf-8 mime part, locate start of p2p header
             QString p2pMimePart = utf8Message.left(mimeEnd + 4);
             int     headerStart = p2pMimePart.toUtf8().size();  // convert mimeEnd index, utf-8 may have double characters
+            int     footerCode  = (int) message[ message.size() - 1 ];  // only take last byte for now.
 
             // Add the mime part and binary p2p part
             logMessage += formatString( p2pMimePart );
-            logMessage += formatP2PMessage(incoming, message, headerStart);
-            logMessage += describeP2PMessage(message, headerStart);
+            logMessage += formatP2PMessage(incoming, message, headerStart, footerCode);
+            logMessage += describeP2PMessage(message, headerStart, footerCode);
           }
         }
         else
@@ -821,8 +831,10 @@
 
 
   // Format a msnp2p message to be displayed.
-  QString NetworkWindow::formatP2PMessage(bool incoming, const QByteArray &message, const int headerStart)
+  QString NetworkWindow::formatP2PMessage(bool incoming, const QByteArray &message, const int headerStart, int footerCode)
   {
+    Q_UNUSED(footerCode);
+
     QString logMessage;
 
     // Prepare to extract certain P2P header fields
@@ -862,7 +874,7 @@
     }
 
     // Show header information
-    logMessage += formatRawData(incoming, message, headerStart, 48, 16);
+    logMessage += formatRawData(incoming, message, headerStart, 48, 4, 16);
 
     // Add header debug details
     logMessage += "<font color='#333333'>";
@@ -887,29 +899,40 @@
     logMessage += "</font>";
 
     // The message a utf-8 (MSNSLP) payload if the session id is zero.
+    const char *dataPtr = ( message.data() + p2pDataStart );
     bool hasSlpPayload  = ( sessionId == 0 && dataSize > 0 );
-    int  dataEnd        = ( p2pDataStart + dataSize );
-    int  realEnd        = ( message.size() );
+    int  safeEnd        = qMin( message.size(), (int) ( p2pDataStart + dataSize ) );
+    int  safeSize       = qMin( (int) dataSize, ( message.size() - p2pDataStart ) );
     if( hasSlpPayload )
     {
       // See if the SLP body is terminated with a final null character (should be).
-      bool hasSlpNullEnd  = ( message[ dataEnd - 1 ] == 0x00 );
+      int  slpLength     = safeSize;
+      bool hasSlpNullEnd = ( message[ p2pDataStart + slpLength - 1 ] == 0x00 );
       if( hasSlpNullEnd )
       {
-        dataEnd--;
+        slpLength--;
       }
 
       // Display the SLP body
-      QString slpMessage = QString::fromUtf8( message.data() + p2pDataStart, qMin( dataSize, (quint32) qMin( dataEnd, realEnd ) - p2pDataStart ) );
-      logMessage += "\n<br>" + formatString(slpMessage);
+      QString slpMessage = QString::fromUtf8( dataPtr, slpLength );
+      logMessage += "\n<br>" + formatString( slpMessage );
 
       // Add message footer
       if( hasSlpNullEnd )
       {
-        // Display the \0 that QString::fromUtf8() silently dropped
-        logMessage += formatRawData( incoming, message, dataEnd, 1 );
+        // Display the \0 that was dropped in the QString::fromUtf8() call.
+        logMessage += formatRawData( incoming, message, slpLength, 1 );
       }
     }
+    else if( dataSize >= 18 && (quint8) message[ p2pDataStart ] == 0x80 )
+    {
+      // Webcam invitation
+      QString camMessage = QString::fromUtf16( reinterpret_cast<const ushort *>( dataPtr + 10 ), ( safeSize - 10 - 1 ) / 2 );
+
+      logMessage += "\n<br>" + formatRawData( incoming, message, p2pDataStart, 10, 1 ) // 1 per col.
+                  + "\n<br>" + formatString( camMessage )
+                  + "\n<br>" + formatRawData( incoming, message, safeEnd - 1, 1 );  // final null.
+    }
     else
     {
       // Don't display the data.
@@ -918,10 +941,10 @@
 
     // Display the remaining bytes, if any.
     // This is the footer when the p2p message is sent over the SB.
-    int remainingBytes = ( message.size() - dataEnd );
+    int remainingBytes = ( message.size() - ( p2pDataStart + dataSize ) );
     if( remainingBytes > 0 )
     {
-      logMessage += "<br>" + formatRawData( incoming, message, dataEnd, remainingBytes );
+      logMessage += "<br>" + formatRawData( incoming, message, safeEnd, remainingBytes );
     }
 
     return logMessage;
@@ -930,7 +953,7 @@
 
 
   // Format a utf-8 string to be displayed
-  QString NetworkWindow::formatRawData(bool incoming, const QByteArray &message, const int start, const int length, const int bytesPerRow)
+  QString NetworkWindow::formatRawData(bool incoming, const QByteArray &message, const int start, const int length, const int bytesPerCol, const int bytesPerRow)
   {
     QString logMessage;
     logMessage += "<tt><font color=";
@@ -949,7 +972,7 @@
         {
           logMessage += "<br>";
         }
-        else if( i % 4 == 0 )
+        else if( i % bytesPerCol == 0 )
         {
           logMessage += " ";
         }
@@ -1325,6 +1348,3 @@
 
 
 #include "networkwindow.moc"
-
-#endif // KMESS_NETWORK_WINDOW
-
Index: dialogs/networkwindow.h
===================================================================
--- dialogs/networkwindow.h	(revision 3566)
+++ dialogs/networkwindow.h	(working copy)
@@ -18,8 +18,6 @@
 
 #include "../kmessdebug.h"
 
-#ifdef KMESS_NETWORK_WINDOW
-
 #ifndef NETWORKWINDOW_H
 #define NETWORKWINDOW_H
 
@@ -94,16 +92,16 @@
     // Return a log description for the mime message
     QString                describeMimeMessage(const QString &mimeMessage);
     // Return a log description for the p2p message
-    QString                describeP2PMessage(const QByteArray &message, const int headerStart);
+    QString                describeP2PMessage(const QByteArray &message, const int headerStart, int footerCode);
     // Format the description to be displayed
     QString                formatDescription(const QString &description);
     // Format the message to be displayed
     QString                formatMessage(ConnectionEntry *entry, bool incoming, const QByteArray &message);
     // Format a msnp2p message to be displayed.
-    QString                formatP2PMessage(bool incoming, const QByteArray &message, const int headerStart);
+    QString                formatP2PMessage(bool incoming, const QByteArray &message, const int headerStart, int footerCode);
     // Format a utf-8 string to be displayed
     QString                formatRawData(bool incoming, const QByteArray &message,
-                                         const int start, const int length, const int bytesPerRow = 32);
+                                         const int start, const int length, const int bytesPerCol = 4, const int bytesPerRow = 32);
     // Format a utf-8 string to be displayed
     QString                formatString(const QString &message);
     // Return the current time.
@@ -140,8 +138,4 @@
 
 };
 
-
-
 #endif
-
-#endif // #ifdef KMESS_NETWORK_WINDOW
