diff --git a/data/glade/message_window.glade b/data/glade/message_window.glade
index b3e2c3cc321f5a959c7151746e1e5f12c193b5c9..bafc686cef30fe0a242d0095e9bb7e715f487f09 100644
--- a/data/glade/message_window.glade
+++ b/data/glade/message_window.glade
@@ -1,7 +1,7 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd">
-<!--*- mode: xml -*-->
+<?xml version="1.0"?>
 <glade-interface>
+  <!-- interface-requires gtk+ 2.14 -->
+  <!-- interface-naming-policy toplevel-contextual -->
   <widget class="GtkWindow" id="message_window">
     <property name="default_width">480</property>
     <property name="default_height">440</property>
@@ -40,6 +40,7 @@
                                 <property name="expand">False</property>
                                 <property name="fill">False</property>
                                 <property name="padding">5</property>
+                                <property name="position">0</property>
                               </packing>
                             </child>
                             <child>
@@ -54,6 +55,9 @@
                                     <property name="label">&lt;span weight="heavy" size="large"&gt;Contact name&lt;/span&gt;</property>
                                     <property name="use_markup">True</property>
                                   </widget>
+                                  <packing>
+                                    <property name="position">0</property>
+                                  </packing>
                                 </child>
                                 <child>
                                   <placeholder/>
@@ -71,14 +75,17 @@
                                   <widget class="GtkImage" id="mood_image">
                                     <property name="no_show_all">True</property>
                                     <property name="stock">None</property>
-                                    <property name="icon_size">1</property>
+                                    <property name="icon-size">1</property>
                                   </widget>
+                                  <packing>
+                                    <property name="position">0</property>
+                                  </packing>
                                 </child>
                                 <child>
                                   <widget class="GtkImage" id="activity_image">
                                     <property name="no_show_all">True</property>
                                     <property name="stock">None</property>
-                                    <property name="icon_size">1</property>
+                                    <property name="icon-size">1</property>
                                   </widget>
                                   <packing>
                                     <property name="position">1</property>
@@ -87,13 +94,33 @@
                                 <child>
                                   <widget class="GtkImage" id="tune_image">
                                     <property name="no_show_all">True</property>
-				    <property name="pixbuf">../emoticons/static/music.png</property>
-                                    <property name="icon_size">1</property>
+                                    <property name="pixbuf">../emoticons/static/music.png</property>
+                                    <property name="icon-size">1</property>
                                   </widget>
                                   <packing>
                                     <property name="position">2</property>
                                   </packing>
                                 </child>
+                                <child>
+                                  <widget class="GtkImage" id="audio_banner_image">
+                                    <property name="visible">True</property>
+                                    <property name="stock">None</property>
+                                    <property name="icon-size">1</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="position">3</property>
+                                  </packing>
+                                </child>
+                                <child>
+                                  <widget class="GtkImage" id="video_banner_image">
+                                    <property name="visible">True</property>
+                                    <property name="stock">None</property>
+                                    <property name="icon-size">1</property>
+                                  </widget>
+                                  <packing>
+                                    <property name="position">4</property>
+                                  </packing>
+                                </child>
                                 <child>
                                   <widget class="GtkAlignment" id="alignment3">
                                     <property name="width_request">11</property>
@@ -103,7 +130,7 @@
                                     </child>
                                   </widget>
                                   <packing>
-                                    <property name="position">3</property>
+                                    <property name="position">5</property>
                                   </packing>
                                 </child>
                               </widget>
@@ -138,6 +165,7 @@
                   <packing>
                     <property name="expand">False</property>
                     <property name="fill">False</property>
+                    <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
@@ -148,35 +176,40 @@
                         <property name="height_request">60</property>
                         <property name="can_focus">True</property>
                         <property name="border_width">3</property>
-                        <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="shadow_type">GTK_SHADOW_IN</property>
+                        <property name="hscrollbar_policy">automatic</property>
+                        <property name="vscrollbar_policy">automatic</property>
+                        <property name="shadow_type">in</property>
                         <child>
                           <placeholder/>
                         </child>
                       </widget>
+                      <packing>
+                        <property name="position">0</property>
+                      </packing>
                     </child>
                     <child>
-                      <widget class="GtkHBox" id="hbox1">
+                      <widget class="GtkHBox" id="hbox">
                         <property name="visible">True</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <child>
                           <widget class="GtkButton" id="authentication_button">
+                            <property name="can_focus">False</property>
+                            <property name="receives_default">False</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="no_show_all">True</property>
-                            <property name="relief">GTK_RELIEF_NONE</property>
+                            <property name="relief">none</property>
                             <property name="focus_on_click">False</property>
-                            <property name="response_id">0</property>
                             <child>
                               <widget class="GtkImage" id="lock_image">
                                 <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                                 <property name="stock">gtk-dialog-authentication</property>
-                                <property name="icon_size">1</property>
+                                <property name="icon-size">1</property>
                               </widget>
                             </child>
                           </widget>
                           <packing>
                             <property name="expand">False</property>
+                            <property name="position">0</property>
                           </packing>
                         </child>
                         <child>
@@ -184,9 +217,9 @@
                             <property name="visible">True</property>
                             <property name="can_focus">True</property>
                             <property name="border_width">3</property>
-                            <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
-                            <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
-                            <property name="shadow_type">GTK_SHADOW_IN</property>
+                            <property name="hscrollbar_policy">never</property>
+                            <property name="vscrollbar_policy">never</property>
+                            <property name="shadow_type">in</property>
                             <child>
                               <placeholder/>
                             </child>
@@ -212,37 +245,40 @@
                     <child>
                       <widget class="GtkButton" id="emoticons_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show a list of emoticons (Alt+M)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="emoticons_button_image">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-missing-image</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="formattings_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show a list of formattings</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image10">
                             <property name="visible">True</property>
                             <property name="stock">gtk-bold</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -268,14 +304,13 @@
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="no_show_all">True</property>
                         <property name="tooltip" translatable="yes">Add this contact to roster (Ctrl+D)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image9">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-add</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -288,16 +323,17 @@
                     <child>
                       <widget class="GtkButton" id="send_file_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Send a file (Ctrl+F)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image3">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -306,70 +342,111 @@
                         <property name="position">4</property>
                       </packing>
                     </child>
+                    <child>
+                      <widget class="GtkToggleButton" id="audio_togglebutton">
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="tooltip" translatable="yes">Toggle audio session</property>
+                        <property name="relief">none</property>
+                        <child>
+                          <widget class="GtkImage" id="audio_image">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-missing-image</property>
+                            <property name="icon-size">1</property>
+                          </widget>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">5</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <widget class="GtkToggleButton" id="video_togglebutton">
+                        <property name="can_focus">True</property>
+                        <property name="receives_default">True</property>
+                        <property name="tooltip" translatable="yes">Toggle video session</property>
+                        <property name="relief">none</property>
+                        <child>
+                          <widget class="GtkImage" id="video_image">
+                            <property name="visible">True</property>
+                            <property name="stock">gtk-missing-image</property>
+                            <property name="icon-size">1</property>
+                          </widget>
+                        </child>
+                      </widget>
+                      <packing>
+                        <property name="expand">False</property>
+                        <property name="position">6</property>
+                      </packing>
+                    </child>
                     <child>
                       <widget class="GtkButton" id="convert_to_gc_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="convert_to_gc_button_image">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-missing-image</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">5</property>
+                        <property name="position">7</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="contact_information_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show the contact's profile (Ctrl+I)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image2">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-info</property>
-                            <property name="icon_size">2</property>
+                            <property name="icon-size">2</property>
                           </widget>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">6</property>
+                        <property name="position">8</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="history_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Browse the chat history (Ctrl+H)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image5">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-justify-fill</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">7</property>
+                        <property name="position">9</property>
                       </packing>
                     </child>
                     <child>
@@ -379,29 +456,30 @@
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">8</property>
+                        <property name="position">10</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="message_window_actions_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show a menu of advanced functions (Alt+A)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image1">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-execute</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">9</property>
+                        <property name="position">11</property>
                       </packing>
                     </child>
                     <child>
@@ -413,14 +491,14 @@
                         </child>
                       </widget>
                       <packing>
-                        <property name="position">10</property>
+                        <property name="position">12</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="send_button">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="response_id">0</property>
+                        <property name="receives_default">False</property>
                         <child>
                           <widget class="GtkAlignment" id="alignment102">
                             <property name="visible">True</property>
@@ -438,6 +516,7 @@
                                   <packing>
                                     <property name="expand">False</property>
                                     <property name="fill">False</property>
+                                    <property name="position">0</property>
                                   </packing>
                                 </child>
                                 <child>
@@ -459,7 +538,7 @@
                       </widget>
                       <packing>
                         <property name="expand">False</property>
-                        <property name="position">11</property>
+                        <property name="position">13</property>
                       </packing>
                     </child>
                   </widget>
@@ -486,6 +565,7 @@
                       <packing>
                         <property name="expand">False</property>
                         <property name="fill">False</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
@@ -493,7 +573,7 @@
                         <property name="visible">True</property>
                         <property name="xalign">0</property>
                         <property name="use_markup">True</property>
-                        <property name="ellipsize">PANGO_ELLIPSIZE_END</property>
+                        <property name="ellipsize">end</property>
                       </widget>
                       <packing>
                         <property name="position">1</property>
@@ -505,14 +585,14 @@
                         <property name="height_request">20</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="receives_default">False</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image1329">
                             <property name="visible">True</property>
                             <property name="ypad">6</property>
                             <property name="stock">gtk-close</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -526,8 +606,8 @@
                 </child>
               </widget>
               <packing>
-                <property name="type">tab</property>
                 <property name="tab_fill">False</property>
+                <property name="type">tab</property>
               </packing>
             </child>
             <child>
@@ -555,6 +635,7 @@
                                 <property name="expand">False</property>
                                 <property name="fill">False</property>
                                 <property name="padding">5</property>
+                                <property name="position">0</property>
                               </packing>
                             </child>
                             <child>
@@ -569,6 +650,9 @@
                                     <property name="label">&lt;span weight="heavy" size="large"&gt;room jid&lt;/span&gt;</property>
                                     <property name="use_markup">True</property>
                                   </widget>
+                                  <packing>
+                                    <property name="position">0</property>
+                                  </packing>
                                 </child>
                                 <child>
                                   <placeholder/>
@@ -586,6 +670,7 @@
                   <packing>
                     <property name="expand">False</property>
                     <property name="fill">False</property>
+                    <property name="position">0</property>
                   </packing>
                 </child>
                 <child>
@@ -609,21 +694,24 @@
                                 <property name="height_request">60</property>
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
-                                <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                                <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                                <property name="shadow_type">GTK_SHADOW_IN</property>
+                                <property name="hscrollbar_policy">automatic</property>
+                                <property name="vscrollbar_policy">automatic</property>
+                                <property name="shadow_type">in</property>
                                 <child>
                                   <placeholder/>
                                 </child>
                               </widget>
+                              <packing>
+                                <property name="position">0</property>
+                              </packing>
                             </child>
                             <child>
                               <widget class="GtkScrolledWindow" id="message_scrolledwindow">
                                 <property name="visible">True</property>
                                 <property name="can_focus">True</property>
-                                <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
-                                <property name="vscrollbar_policy">GTK_POLICY_NEVER</property>
-                                <property name="shadow_type">GTK_SHADOW_IN</property>
+                                <property name="hscrollbar_policy">never</property>
+                                <property name="vscrollbar_policy">never</property>
+                                <property name="shadow_type">in</property>
                                 <child>
                                   <placeholder/>
                                 </child>
@@ -634,6 +722,9 @@
                               </packing>
                             </child>
                           </widget>
+                          <packing>
+                            <property name="position">0</property>
+                          </packing>
                         </child>
                       </widget>
                       <packing>
@@ -646,9 +737,9 @@
                         <property name="width_request">100</property>
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
-                        <property name="hscrollbar_policy">GTK_POLICY_NEVER</property>
-                        <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
-                        <property name="shadow_type">GTK_SHADOW_IN</property>
+                        <property name="hscrollbar_policy">never</property>
+                        <property name="vscrollbar_policy">automatic</property>
+                        <property name="shadow_type">in</property>
                         <child>
                           <widget class="GtkTreeView" id="list_treeview">
                             <property name="visible">True</property>
@@ -677,41 +768,45 @@
                         <child>
                           <widget class="GtkButton" id="emoticons_button">
                             <property name="visible">True</property>
+                            <property name="can_focus">False</property>
+                            <property name="receives_default">False</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="tooltip" translatable="yes">Show a list of emoticons (Alt+M)</property>
-                            <property name="relief">GTK_RELIEF_NONE</property>
-                            <property name="response_id">0</property>
+                            <property name="relief">none</property>
                             <child>
                               <widget class="GtkImage" id="emoticons_button_image">
                                 <property name="visible">True</property>
                                 <property name="stock">gtk-missing-image</property>
-                                <property name="icon_size">1</property>
+                                <property name="icon-size">1</property>
                               </widget>
                             </child>
                           </widget>
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">False</property>
+                            <property name="position">0</property>
                           </packing>
                         </child>
                       </widget>
                       <packing>
                         <property name="expand">False</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
                       <widget class="GtkButton" id="formattings_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show a list of formattings</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkImage" id="image11">
                             <property name="visible">True</property>
                             <property name="stock">gtk-bold</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -733,16 +828,17 @@
                     <child>
                       <widget class="GtkButton" id="change_nick_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Change your nickname (Ctrl+N)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image4">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-edit</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -755,16 +851,17 @@
                     <child>
                       <widget class="GtkButton" id="change_subject_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Change the room's subject (Alt+T)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image6">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-properties</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -777,17 +874,18 @@
                     <child>
                       <widget class="GtkButton" id="bookmark_button">
                         <property name="visible">True</property>
-                        <property name="no_show_all">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+                        <property name="no_show_all">True</property>
                         <property name="tooltip" translatable="yes">Bookmark this room (Ctrl+B)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image7">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-add</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -800,16 +898,17 @@
                     <child>
                       <widget class="GtkButton" id="history_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Browse the chat history (Ctrl+H)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image8">
                             <property name="visible">True</property>
                             <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                             <property name="stock">gtk-justify-fill</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -832,11 +931,12 @@
                     <child>
                       <widget class="GtkButton" id="muc_window_actions_button">
                         <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="receives_default">False</property>
                         <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
                         <property name="tooltip" translatable="yes">Show a menu of advanced functions (Alt+A)</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
+                        <property name="relief">none</property>
                         <property name="focus_on_click">False</property>
-                        <property name="response_id">0</property>
                         <child>
                           <widget class="GtkAlignment" id="alignment104">
                             <property name="visible">True</property>
@@ -846,7 +946,7 @@
                               <widget class="GtkImage" id="image1344">
                                 <property name="visible">True</property>
                                 <property name="stock">gtk-execute</property>
-                                <property name="icon_size">1</property>
+                                <property name="icon-size">1</property>
                               </widget>
                             </child>
                           </widget>
@@ -874,7 +974,7 @@
                       <widget class="GtkButton" id="send_button">
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="response_id">0</property>
+                        <property name="receives_default">False</property>
                         <child>
                           <widget class="GtkAlignment" id="alignment105">
                             <property name="visible">True</property>
@@ -892,6 +992,7 @@
                                   <packing>
                                     <property name="expand">False</property>
                                     <property name="fill">False</property>
+                                    <property name="position">0</property>
                                   </packing>
                                 </child>
                                 <child>
@@ -944,6 +1045,7 @@
                       <packing>
                         <property name="expand">False</property>
                         <property name="fill">False</property>
+                        <property name="position">0</property>
                       </packing>
                     </child>
                     <child>
@@ -963,14 +1065,14 @@
                         <property name="height_request">20</property>
                         <property name="visible">True</property>
                         <property name="can_focus">True</property>
-                        <property name="relief">GTK_RELIEF_NONE</property>
-                        <property name="response_id">0</property>
+                        <property name="receives_default">False</property>
+                        <property name="relief">none</property>
                         <child>
                           <widget class="GtkImage" id="image1347">
                             <property name="visible">True</property>
                             <property name="ypad">6</property>
                             <property name="stock">gtk-close</property>
-                            <property name="icon_size">1</property>
+                            <property name="icon-size">1</property>
                           </widget>
                         </child>
                       </widget>
@@ -984,9 +1086,9 @@
                 </child>
               </widget>
               <packing>
-                <property name="type">tab</property>
                 <property name="position">1</property>
                 <property name="tab_fill">False</property>
+                <property name="type">tab</property>
               </packing>
             </child>
           </widget>
diff --git a/data/glade/voip_call_received_dialog.glade b/data/glade/voip_call_received_dialog.glade
new file mode 100644
index 0000000000000000000000000000000000000000..b4314fe2232c14d6cbeb7a8e54552830e6e7a6a5
--- /dev/null
+++ b/data/glade/voip_call_received_dialog.glade
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<glade-interface>
+  <!-- interface-requires gtk+ 2.14 -->
+  <!-- interface-naming-policy toplevel-contextual -->
+  <widget class="GtkMessageDialog" id="voip_call_received_messagedialog">
+    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+    <property name="border_width">5</property>
+    <property name="resizable">False</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="type_hint">dialog</property>
+    <property name="skip_taskbar_hint">True</property>
+    <property name="message_type">question</property>
+    <property name="buttons">yes-no</property>
+    <property name="text">&lt;b&gt;&lt;big&gt;Incoming call&lt;/big&gt;&lt;/b&gt;</property>
+    <property name="use_markup">True</property>
+    <signal name="destroy" handler="on_voip_call_received_messagedialog_destroy"/>
+    <signal name="close" handler="on_voip_call_received_messagedialog_close"/>
+    <signal name="response" handler="on_voip_call_received_messagedialog_response"/>
+    <child internal-child="vbox">
+      <widget class="GtkVBox" id="dialog-vbox4">
+        <property name="visible">True</property>
+        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <widget class="GtkHButtonBox" id="dialog-action_area4">
+            <property name="visible">True</property>
+            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+            <property name="layout_style">end</property>
+          </widget>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </widget>
+    </child>
+  </widget>
+</glade-interface>
diff --git a/src/chat_control.py b/src/chat_control.py
index 29e74c814290c5bc9289b084442f869dc41fd470..dc5c4a40fdde14789cd5230e429cab9dc9cb9eb8 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -51,6 +51,7 @@ from common.logger import constants
 from common.pep import MOODS, ACTIVITIES
 from common.xmpp.protocol import NS_XHTML, NS_XHTML_IM, NS_FILE, NS_MUC
 from common.xmpp.protocol import NS_RECEIPTS, NS_ESESSION
+from common.xmpp.protocol import NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO, NS_JINGLE_ICE_UDP
 
 from command_system.implementation.middleware import ChatCommandProcessor
 from command_system.implementation.middleware import CommandTools
@@ -1173,6 +1174,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
 ################################################################################
 class ChatControl(ChatControlBase):
 	'''A control for standard 1-1 chat'''
+	(
+		JINGLE_STATE_NOT_AVAILABLE,
+		JINGLE_STATE_AVAILABLE,
+		JINGLE_STATE_CONNECTING,
+		JINGLE_STATE_CONNECTION_RECEIVED,
+		JINGLE_STATE_CONNECTED,
+		JINGLE_STATE_ERROR
+	) = range(6)
+
 	TYPE_ID = message_control.TYPE_CHAT
 	old_msg_kind = None # last kind of the printed message
 
@@ -1200,6 +1210,14 @@ class ChatControl(ChatControlBase):
 			self._on_add_to_roster_menuitem_activate)
 		self.handlers[id_] = self._add_to_roster_button
 
+		self._audio_button = self.xml.get_widget('audio_togglebutton')
+		id_ = self._audio_button.connect('toggled', self.on_audio_button_toggled)
+		self.handlers[id_] = self._audio_button
+
+		self._video_button = self.xml.get_widget('video_togglebutton')
+		id_ = self._video_button.connect('toggled', self.on_video_button_toggled)
+		self.handlers[id_] = self._video_button
+
 		self._send_file_button = self.xml.get_widget('send_file_button')
 		# add a special img for send file button
 		path_to_upload_img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'upload.png')
@@ -1241,6 +1259,13 @@ class ChatControl(ChatControlBase):
 		img.set_from_pixbuf(gtkgui_helpers.load_icon(
 			'muc_active').get_pixbuf())
 
+		self._audio_banner_image = self.xml.get_widget('audio_banner_image')
+		self._video_banner_image = self.xml.get_widget('video_banner_image')
+		self.audio_sid = None
+		self.audio_state = self.JINGLE_STATE_NOT_AVAILABLE
+		self.video_sid = None
+		self.video_state = self.JINGLE_STATE_NOT_AVAILABLE
+
 		self.update_toolbar()
 
 		self._mood_image = self.xml.get_widget('mood_image')
@@ -1345,6 +1370,38 @@ class ChatControl(ChatControlBase):
 		else:
 			self._add_to_roster_button.hide()
 
+		# Jingle detection
+		if gajim.capscache.is_supported(self.contact, NS_JINGLE_ICE_UDP) and \
+		gajim.HAVE_FARSIGHT and self.contact.resource:
+			if gajim.capscache.is_supported(self.contact, NS_JINGLE_RTP_AUDIO):
+				if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
+					self.set_audio_state('available')
+			else:
+				self.set_audio_state('not_available')
+
+			if gajim.capscache.is_supported(self.contact, NS_JINGLE_RTP_VIDEO):
+				if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
+					self.set_video_state('available')
+			else:
+				self.set_video_state('not_available')
+		else:
+			if self.audio_state != self.JINGLE_STATE_NOT_AVAILABLE:
+				self.set_audio_state('not_available')
+			if self.video_state != self.JINGLE_STATE_NOT_AVAILABLE:
+				self.set_video_state('not_available')
+
+		# Audio buttons
+		if self.audio_state == self.JINGLE_STATE_NOT_AVAILABLE:
+			self._audio_button.set_sensitive(False)
+		else:
+			self._audio_button.set_sensitive(True)
+
+		# Video buttons
+		if self.video_state == self.JINGLE_STATE_NOT_AVAILABLE:
+			self._video_button.set_sensitive(False)
+		else:
+			self._video_button.set_sensitive(True)
+
 		# Send file
 		if gajim.capscache.is_supported(self.contact, NS_FILE) and \
 		self.contact.resource:
@@ -1477,6 +1534,34 @@ class ChatControl(ChatControlBase):
 		else:
 			self._tune_image.hide()
 
+	def _update_jingle(self, jingle_type):
+		if jingle_type not in ('audio', 'video'):
+			return
+		if self.__dict__[jingle_type + '_state'] in (
+		self.JINGLE_STATE_NOT_AVAILABLE, self.JINGLE_STATE_AVAILABLE):
+			self.__dict__['_' + jingle_type + '_banner_image'].hide()
+		else:
+			self.__dict__['_' + jingle_type + '_banner_image'].show()
+		if self.audio_state == self.JINGLE_STATE_CONNECTING:
+			self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock(
+				gtk.STOCK_CONVERT, 1)
+		elif self.audio_state == self.JINGLE_STATE_CONNECTION_RECEIVED:
+			self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock(
+				gtk.STOCK_NETWORK, 1)
+		elif self.audio_state == self.JINGLE_STATE_CONNECTED:
+			self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock(
+				gtk.STOCK_CONNECT, 1)
+		elif self.audio_state == self.JINGLE_STATE_ERROR:
+			self.__dict__['_' + jingle_type + '_banner_image'].set_from_stock(
+				gtk.STOCK_DIALOG_WARNING, 1)
+		self.update_toolbar()
+
+	def update_audio(self):
+		self._update_jingle('audio')
+
+	def update_video(self):
+		self._update_jingle('video')
+
 	def change_resource(self, resource):
 		old_full_jid = self.get_full_jid()
 		self.resource = resource
@@ -1490,6 +1575,52 @@ class ChatControl(ChatControlBase):
 		# update MessageWindow._controls
 		self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
 
+	def _set_jingle_state(self, jingle_type, state, sid=None, reason=None):
+		if jingle_type not in ('audio', 'video'):
+			return
+		if state in ('connecting', 'connected', 'stop') and reason:
+			str = _('%(type)s state : %(state)s, reason: %(reason)s') % {
+				'type': jingle_type.capitalize(), 'state': state, 'reason': reason}
+			self.print_conversation(str, 'info')
+
+		states = {'not_available': self.JINGLE_STATE_NOT_AVAILABLE,
+			'available': self.JINGLE_STATE_AVAILABLE,
+			'connecting': self.JINGLE_STATE_CONNECTING,
+			'connection_received': self.JINGLE_STATE_CONNECTION_RECEIVED,
+			'connected': self.JINGLE_STATE_CONNECTED,
+			'stop': self.JINGLE_STATE_AVAILABLE,
+			'error': self.JINGLE_STATE_ERROR}
+
+		if state in states:
+			jingle_state = states[state]
+			if self.__dict__[jingle_type + '_state'] == jingle_state:
+				return
+			self.__dict__[jingle_type + '_state'] = jingle_state
+
+		# Destroy existing session with the user when he signs off
+		# We need to do that before modifying the sid
+		if state == 'not_available':
+			gajim.connections[self.account].delete_jingle_session(
+				self.contact.get_full_jid(), self.__dict__[jingle_type + '_sid'])
+
+		if state in ('not_available', 'available', 'stop'):
+			self.__dict__[jingle_type + '_sid'] = None
+		if state in ('connection_received', 'connecting'):
+			self.__dict__[jingle_type + '_sid'] = sid
+
+		if state in ('connecting', 'connected', 'connection_received'):
+			self.__dict__['_' + jingle_type + '_button'].set_active(True)
+		elif state in ('not_available', 'stop'):
+			self.__dict__['_' + jingle_type + '_button'].set_active(False)
+
+		eval('self.update_' + jingle_type)()
+
+	def set_audio_state(self, state, sid=None, reason=None):
+		self._set_jingle_state('audio', state, sid=sid, reason=reason)
+
+	def set_video_state(self, state, sid=None, reason=None):
+		self._set_jingle_state('video', state, sid=sid, reason=reason)
+
 	def on_avatar_eventbox_enter_notify_event(self, widget, event):
 		'''
 		we enter the eventbox area so we under conditions add a timeout
@@ -1688,6 +1819,34 @@ class ChatControl(ChatControlBase):
 		banner_name_label.set_markup(label_text)
 		banner_name_label.set_tooltip_text(label_tooltip)
 
+	def on_audio_button_toggled(self, widget):
+		if widget.get_active():
+			if self.audio_state == self.JINGLE_STATE_AVAILABLE:
+				sid = gajim.connections[self.account].startVoIP(
+					self.contact.get_full_jid())
+				self.set_audio_state('connecting', sid)
+		else:
+			session = gajim.connections[self.account].get_jingle_session(
+				self.contact.get_full_jid(), self.audio_sid)
+			if session:
+				content = session.get_content('audio')
+				if content:
+					session.remove_content(content.creator, content.name)
+
+	def on_video_button_toggled(self, widget):
+		if widget.get_active():
+			if self.video_state == self.JINGLE_STATE_AVAILABLE:
+				sid = gajim.connections[self.account].startVideoIP(
+					self.contact.get_full_jid())
+				self.set_video_state('connecting', sid)
+		else:
+			session = gajim.connections[self.account].get_jingle_session(
+				self.contact.get_full_jid(), self.video_sid)
+			if session:
+				content = session.get_content('video')
+				if content:
+					session.remove_content(content.creator, content.name)
+
 	def _toggle_gpg(self):
 		if not self.gpg_is_active and not self.contact.keyID:
 			dialogs.ErrorDialog(_('No GPG key assigned'),
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index f902812462810d651a87d7d40cad90fceee78bf4..f48a98d0a1362d9f21a53ccb930f39f9f43a4436 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -52,6 +52,14 @@ from common import exceptions
 from common.commands import ConnectionCommands
 from common.pubsub import ConnectionPubSub
 from common.caps import ConnectionCaps
+if gajim.HAVE_FARSIGHT:
+	from common.jingle import ConnectionJingle
+else:
+	class ConnectionJingle():
+		def __init__(self):
+			pass
+		def _JingleCB(self, con, stanza):
+			pass
 
 from common import dbus_support
 if dbus_support.supported:
@@ -1445,12 +1453,13 @@ sent a message to.'''
 
 		return sess
 
-class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase):
+class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
 	def __init__(self):
 		ConnectionVcard.__init__(self)
 		ConnectionBytestream.__init__(self)
 		ConnectionCommands.__init__(self)
 		ConnectionPubSub.__init__(self)
+		ConnectionJingle.__init__(self)
 		ConnectionHandlersBase.__init__(self)
 		self.gmail_url = None
 
@@ -2807,6 +2816,10 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
 			common.xmpp.NS_PRIVACY)
 		con.RegisterHandler('iq', self._PubSubCB, 'result')
 		con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
+		con.RegisterHandler('iq', self._JingleCB, 'result')
+		con.RegisterHandler('iq', self._JingleCB, 'error')
+		con.RegisterHandler('iq', self._JingleCB, 'set',
+			common.xmpp.NS_JINGLE)
 		con.RegisterHandler('iq', self._ErrorCB, 'error')
 		con.RegisterHandler('iq', self._IqCB)
 		con.RegisterHandler('iq', self._StanzaArrivedCB)
diff --git a/src/common/events.py b/src/common/events.py
index 82017112aa9047418b5494e89eaab4d2b1004653..c69fc27d1bdcc4a94d6ad9ca3f535a697abf4a56 100644
--- a/src/common/events.py
+++ b/src/common/events.py
@@ -33,7 +33,7 @@ class Event:
 		''' type_ in chat, normal, file-request, file-error, file-completed,
 		file-request-error, file-send-error, file-stopped, gc_msg, pm,
 		printed_chat, printed_gc_msg, printed_marked_gc_msg, printed_pm,
-		gc-invitation, subscription_request, unsubscribed
+		gc-invitation, subscription_request, unsubscribedm jingle-incoming
 		parameters is (per type_):
 			chat, normal, pm: [message, subject, kind, time, encrypted, resource,
 			msg_id]
@@ -46,6 +46,7 @@ class Event:
 			gc-invitation: [room_jid, reason, password, is_continued]
 			subscription_request: [text, nick]
 			unsubscribed: contact
+			jingle-incoming: (fulljid, sessionid, content_types)
 		'''
 		self.type_ = type_
 		self.time_ = time_
diff --git a/src/common/gajim.py b/src/common/gajim.py
index 3511fe2d9fbacc0020b9905f1442a7a3a832008c..0f340114a98674e630a43ba71802bd69bb8914b0 100644
--- a/src/common/gajim.py
+++ b/src/common/gajim.py
@@ -189,6 +189,11 @@ try:
 except ImportError:
 	HAVE_INDICATOR = False
 
+HAVE_FARSIGHT = True
+try:
+	import farsight, gst
+except ImportError:
+	HAVE_FARSIGHT = False
 gajim_identity = {'type': 'pc', 'category': 'client', 'name': 'Gajim'}
 gajim_common_features = [xmpp.NS_BYTESTREAM, xmpp.NS_SI, xmpp.NS_FILE,
 	xmpp.NS_MUC, xmpp.NS_MUC_USER, xmpp.NS_MUC_ADMIN, xmpp.NS_MUC_OWNER,
diff --git a/src/common/helpers.py b/src/common/helpers.py
index 106b0ee344c81dff9bf0c2c2bc7c44a5ec13925d..1282e6e42bff95e5ca33757aad6c6fcf548505cd 100644
--- a/src/common/helpers.py
+++ b/src/common/helpers.py
@@ -1357,6 +1357,12 @@ def update_optional_features(account = None):
 			gajim.gajim_optional_features[a].append(xmpp.NS_ESESSION)
 		if gajim.config.get_per('accounts', a, 'answer_receipts'):
 			gajim.gajim_optional_features[a].append(xmpp.NS_RECEIPTS)
+		if gajim.HAVE_FARSIGHT:
+			gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE)
+			gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP)
+			gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_AUDIO)
+			gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_RTP_VIDEO)
+			gajim.gajim_optional_features[a].append(xmpp.NS_JINGLE_ICE_UDP)
 		gajim.caps_hash[a] = compute_caps_hash([gajim.gajim_identity],
 			gajim.gajim_common_features + gajim.gajim_optional_features[a])
 		# re-send presence with new hash
diff --git a/src/common/jingle.py b/src/common/jingle.py
new file mode 100644
index 0000000000000000000000000000000000000000..f058865334c1ce48e603eb318143692fa904fae4
--- /dev/null
+++ b/src/common/jingle.py
@@ -0,0 +1,1165 @@
+##
+## Copyright (C) 2006 Gajim Team
+##
+## 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 only.
+##
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+##
+''' Handles the jingle signalling protocol. '''
+
+#TODO:
+# * things in XEP 0166, including:
+#   - 'senders' attribute of 'content' element
+#   - security preconditions
+#   * actions:
+#     - content-modify
+#     - description-info, session-info
+#     - security-info
+#     - transport-accept, transport-reject
+#   * sid/content related:
+#      - tiebreaking
+#      - if there already is a session, use it
+# * things in XEP 0176, including:
+#      - http://xmpp.org/extensions/xep-0176.html#protocol-restarts
+#      - http://xmpp.org/extensions/xep-0176.html#fallback
+# * XEP 0177 (raw udp)
+
+# * UI:
+#   - make state and codec informations available to the user
+#   - video integration
+#   * config:
+#     - codecs
+#     - STUN
+
+# * DONE: figure out why it doesn't work with pidgin:
+#     That's a bug in pidgin: http://xmpp.org/extensions/xep-0176.html#protocol-checks
+
+# * timeout
+
+# * split this file in several modules
+#   For example, a file dedicated for XEP0166, one for XEP0176,
+#   and one for XEP0167
+
+# * handle different kinds of sink and src elements
+
+import gajim
+import xmpp
+import helpers
+
+import farsight, gst
+
+def get_first_gst_element(elements):
+	''' Returns, if it exists, the first available element of the list. '''
+	for name in elements:
+		factory = gst.element_factory_find(name)
+		if factory:
+			return factory.create()
+
+#FIXME: Move it to JingleSession.States?
+class JingleStates(object):
+	''' States in which jingle session may exist. '''
+	ended = 0
+	pending = 1
+	active = 2
+
+#FIXME: Move it to JingleTransport.Type?
+class TransportType(object):
+	''' Possible types of a JingleTransport '''
+	datagram = 1
+	streaming = 2
+
+class OutOfOrder(Exception):
+	''' Exception that should be raised when an action is received when in the wrong state. '''
+
+class TieBreak(Exception):
+	''' Exception that should be raised in case of a tie, when we overrule the other action. '''
+
+class JingleSession(object):
+	''' This represents one jingle session. '''
+	def __init__(self, con, weinitiate, jid, sid=None):
+		''' con -- connection object,
+			 weinitiate -- boolean, are we the initiator?
+			 jid - jid of the other entity'''
+		self.contents = {} # negotiated contents
+		self.connection = con # connection to use
+		# our full jid
+		self.ourjid = gajim.get_jid_from_account(self.connection.name) + '/' + \
+			con.server_resource
+		self.peerjid = jid # jid we connect to
+		# jid we use as the initiator
+		self.initiator = weinitiate and self.ourjid or self.peerjid
+		# jid we use as the responder
+		self.responder = weinitiate and self.peerjid or self.ourjid
+		# are we an initiator?
+		self.weinitiate = weinitiate
+		# what state is session in? (one from JingleStates)
+		self.state = JingleStates.ended
+		if not sid:
+			sid = con.connection.getAnID()
+		self.sid = sid # sessionid
+
+		self.accepted = True # is this session accepted by user
+
+		# callbacks to call on proper contents
+		# use .prepend() to add new callbacks, especially when you're going
+		# to send error instead of ack
+		self.callbacks = {
+			'content-accept':	[self.__contentAcceptCB, self.__broadcastCB,
+				self.__defaultCB],
+			'content-add':		[self.__contentAddCB, self.__broadcastCB,
+				self.__defaultCB], #TODO
+			'content-modify':	[self.__defaultCB], #TODO
+			'content-reject':	[self.__defaultCB, self.__contentRemoveCB], #TODO
+			'content-remove':	[self.__defaultCB, self.__contentRemoveCB],
+			'description-info':	[self.__broadcastCB, self.__defaultCB], #TODO
+			'security-info':	[self.__defaultCB], #TODO
+			'session-accept':	[self.__sessionAcceptCB, self.__contentAcceptCB,
+				self.__broadcastCB, self.__defaultCB],
+			'session-info':		[self.__sessionInfoCB, self.__broadcastCB, self.__defaultCB],
+			'session-initiate':	[self.__sessionInitiateCB, self.__broadcastCB,
+				self.__defaultCB],
+			'session-terminate':	[self.__sessionTerminateCB, self.__broadcastAllCB,
+				self.__defaultCB],
+			'transport-info':	[self.__broadcastCB, self.__defaultCB],
+			'transport-replace':	[self.__broadcastCB, self.__transportReplaceCB], #TODO
+			'transport-accept':	[self.__defaultCB], #TODO
+			'transport-reject':	[self.__defaultCB], #TODO
+			'iq-result':		[],
+			'iq-error':		[self.__errorCB],
+		}
+
+	''' Interaction with user '''
+	def approve_session(self):
+		''' Called when user accepts session in UI (when we aren't the initiator).
+		'''
+		self.accept_session()
+
+	def decline_session(self):
+		''' Called when user declines session in UI (when we aren't the initiator)
+		'''
+		reason = xmpp.Node('reason')
+		reason.addChild('decline')
+		self._session_terminate(reason)
+
+	def approve_content(self, media):
+		content = self.get_content(media)
+		if content:
+			content.accepted = True
+			self.on_session_state_changed(content)
+
+	def reject_content(self, media):
+		content = self.get_content(media)
+		if content:
+			if self.state == JingleStates.active:
+				self.__content_reject(content)
+			content.destroy()
+			self.on_session_state_changed()
+
+	def end_session(self):
+		''' Called when user stops or cancel session in UI. '''
+		reason = xmpp.Node('reason')
+		if self.state == JingleStates.active:
+			reason.addChild('success')
+		else:
+			reason.addChild('cancel')
+		self._session_terminate(reason)
+
+	''' Middle-level functions to manage contents. Handle local content
+	cache and send change notifications. '''
+	def get_content(self, media=None):
+		if media == 'audio':
+			cls = JingleVoIP
+		elif media == 'video':
+			cls = JingleVideo
+		#elif media == None:
+		#	cls = JingleContent
+		else:
+			return None
+
+		for content in self.contents.values():
+			if isinstance(content, cls):
+				return content
+
+	def add_content(self, name, content, creator='we'):
+		''' Add new content to session. If the session is active,
+		this will send proper stanza to update session. 
+		Creator must be one of ('we', 'peer', 'initiator', 'responder')'''
+		assert creator in ('we', 'peer', 'initiator', 'responder')
+
+		if (creator == 'we' and self.weinitiate) or (creator == 'peer' and \
+		not self.weinitiate):
+			creator = 'initiator'
+		elif (creator == 'peer' and self.weinitiate) or (creator == 'we' and \
+		not self.weinitiate):
+			creator = 'responder'
+		content.creator = creator
+		content.name = name
+		self.contents[(creator, name)] = content
+
+		if (creator == 'initiator') == self.weinitiate:
+			# The content is from us, accept it
+			content.accepted = True
+
+	def remove_content(self, creator, name):
+		''' We do not need this now '''
+		#TODO:
+		if (creator, name) in self.contents:
+			content = self.contents[(creator, name)]
+			if len(self.contents) > 1:
+				self.__content_remove(content)
+			self.contents[(creator, name)].destroy()
+		if len(self.contents) == 0:
+			self.end_session()
+
+	def modify_content(self, creator, name, *someother):
+		''' We do not need this now '''
+		pass
+
+	def on_session_state_changed(self, content=None):
+		if self.state == JingleStates.ended:
+			# Session not yet started, only one action possible: session-initiate
+			if self.is_ready() and self.weinitiate:
+				self.__session_initiate()
+		elif self.state == JingleStates.pending:
+			# We can either send a session-accept or a content-add
+			if self.is_ready() and not self.weinitiate:
+				self.__session_accept()
+			elif content and (content.creator == 'initiator') == self.weinitiate:
+				self.__content_add(content)
+			elif content and self.weinitiate:
+				self.__content_accept(content)
+		elif self.state == JingleStates.active:
+			# We can either send a content-add or a content-accept
+			if not content:
+				return
+			if (content.creator == 'initiator') == self.weinitiate:
+				# We initiated this content. It's a pending content-add.
+				self.__content_add(content)
+			else:
+				# The other side created this content, we accept it.
+				self.__content_accept(content)
+
+	def is_ready(self):
+		''' Returns True when all codecs and candidates are ready
+		(for all contents). '''
+		return (all((content.is_ready() for content in self.contents.itervalues()))
+			and self.accepted)
+
+	''' Middle-level function to do stanza exchange. '''
+	def accept_session(self):
+		''' Mark the session as accepted. '''
+		self.accepted = True
+		self.on_session_state_changed()
+
+	def start_session(self):
+		''' Mark the session as ready to be started. '''
+		self.accepted = True
+		self.on_session_state_changed()
+
+	def send_session_info(self):
+		pass
+
+	def send_content_accept(self, content):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('content-accept')
+		jingle.addChild(node=content)
+		self.connection.connection.send(stanza)
+
+	def send_transport_info(self, content):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('transport-info')
+		jingle.addChild(node=content)
+		self.connection.connection.send(stanza)
+
+	''' Session callbacks. '''
+	def stanzaCB(self, stanza):
+		''' A callback for ConnectionJingle. It gets stanza, then
+		tries to send it to all internally registered callbacks.
+		First one to raise xmpp.NodeProcessed breaks function.'''
+		jingle = stanza.getTag('jingle')
+		error = stanza.getTag('error')
+		if error:
+			# it's an iq-error stanza
+			action = 'iq-error'
+		elif jingle:
+			# it's a jingle action
+			action = jingle.getAttr('action')
+			if action not in self.callbacks:
+				self.__send_error(stanza, 'bad_request')
+				return
+			#FIXME: If we aren't initiated and it's not a session-initiate...
+			if action != 'session-initiate' and self.state == JingleStates.ended:
+				self.__send_error(stanza, 'item-not-found', 'unknown-session')
+				return
+		else:
+			# it's an iq-result (ack) stanza
+			action = 'iq-result'
+
+		callables = self.callbacks[action]
+
+		try:
+			for callable in callables:
+				callable(stanza=stanza, jingle=jingle, error=error, action=action)
+		except xmpp.NodeProcessed:
+			pass
+		except TieBreak:
+			self.__send_error(stanza, 'conflict', 'tiebreak')
+		except OutOfOrder:
+			self.__send_error(stanza, 'unexpected-request', 'out-of-order')#FIXME
+
+	def __defaultCB(self, stanza, jingle, error, action):
+		''' Default callback for action stanzas -- simple ack
+		and stop processing. '''
+		response = stanza.buildReply('result')
+		self.connection.connection.send(response)
+
+	def __errorCB(self, stanza, jingle, error, action):
+		#FIXME
+		text = error.getTagData('text')
+		jingle_error = None
+		xmpp_error = None
+		for child in error.getChildren():
+			if child.getNamespace() == xmpp.NS_JINGLE_ERRORS:
+				jingle_error = child.getName()
+			elif child.getNamespace() == xmpp.NS_STANZAS:
+				xmpp_error = child.getName()
+		self.__dispatch_error(xmpp_error, jingle_error, text)
+		#FIXME: Not sure when we would want to do that...
+		if xmpp_error == 'item-not-found':
+			self.connection.delete_jingle_session(self.peerjid, self.sid)
+
+	def __transportReplaceCB(self, stanza, jingle, error, action):
+		for content in jingle.iterTags('content'):
+			creator = content['creator']
+			name = content['name']
+			if (creator, name) in self.contents:
+				transport_ns = content.getTag('transport').getNamespace()
+				if transport_ns == xmpp.JINGLE_ICE_UDP:
+					#FIXME: We don't manage anything else than ICE-UDP now...
+					#What was the previous transport?!?
+					#Anyway, content's transport is not modifiable yet
+					pass
+				else:
+					stanza, jingle = self.__make_jingle('transport-reject')
+					content = jingle.setTag('content', attrs={'creator': creator,
+						'name': name})
+					content.setTag('transport', namespace=transport_ns)
+					self.connection.connection.send(stanza)
+					raise xmpp.NodeProcessed
+			else:
+				#FIXME: This ressource is unknown to us, what should we do?
+				#For now, reject the transport
+				stanza, jingle = self.__make_jingle('transport-reject')
+				c = jingle.setTag('content', attrs={'creator': creator,
+					'name': name})
+				c.setTag('transport', namespace=transport_ns)
+				self.connection.connection.send(stanza)
+				raise xmpp.NodeProcessed
+
+	def __sessionInfoCB(self, stanza, jingle, error, action):
+		#TODO: ringing, active, (un)hold, (un)mute
+		payload = jingle.getPayload()
+		if len(payload) > 0:
+			self.__send_error(stanza, 'feature-not-implemented', 'unsupported-info')
+			raise xmpp.NodeProcessed
+
+	def __contentRemoveCB(self, stanza, jingle, error, action):
+		for content in jingle.iterTags('content'):
+			creator = content['creator']
+			name = content['name']
+			if (creator, name) in self.contents:
+				content = self.contents[(creator, name)]
+				#TODO: this will fail if content is not an RTP content
+				self.connection.dispatch('JINGLE_DISCONNECTED',
+					(self.peerjid, self.sid, content.media, 'removed'))
+				content.destroy()
+		if len(self.contents) == 0:
+			reason = xmpp.Node('reason')
+			reason.setTag('success')
+			self._session_terminate(reason)
+
+	def __sessionAcceptCB(self, stanza, jingle, error, action):
+		if self.state != JingleStates.pending: #FIXME
+			raise OutOfOrder
+		self.state = JingleStates.active
+
+	def __contentAcceptCB(self, stanza, jingle, error, action):
+		''' Called when we get content-accept stanza or equivalent one
+		(like session-accept).'''
+		# check which contents are accepted
+		for content in jingle.iterTags('content'):
+			creator = content['creator']
+			name = content['name']#TODO...
+
+	def __contentAddCB(self, stanza, jingle, error, action):
+		if self.state == JingleStates.ended:
+			raise OutOfOrder
+
+		parse_result = self.__parse_contents(jingle)
+		contents = parse_result[2]
+		rejected_contents = parse_result[3]
+
+		for name, creator in rejected_contents:
+			#TODO:
+			content = JingleContent()
+			self.add_content(name, content, creator)
+			self.__content_reject(content)
+			self.contents[(content.creator, content.name)].destroy()
+
+		self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
+			contents))
+
+	def __sessionInitiateCB(self, stanza, jingle, error, action):
+		''' We got a jingle session request from other entity,
+		therefore we are the receiver... Unpack the data,
+		inform the user. '''
+
+		if self.state != JingleStates.ended:
+			raise OutOfOrder
+
+		self.initiator = jingle['initiator']
+		self.responder = self.ourjid
+		self.peerjid = self.initiator
+		self.accepted = False	# user did not accept this session yet
+
+		# TODO: If the initiator is unknown to the receiver (e.g., via presence
+		# subscription) and the receiver has a policy of not communicating via
+		# Jingle with unknown entities, it SHOULD return a <service-unavailable/>
+		# error.
+
+		# Lets check what kind of jingle session does the peer want
+		contents_ok, transports_ok, contents, pouet = self.__parse_contents(jingle)
+
+		# If there's no content we understand...
+		if not contents_ok:
+			# TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
+			reason = xmpp.Node('reason')
+			reason.setTag('unsupported-applications')
+			self.__defaultCB(stanza, jingle, error, action)
+			self._session_terminate(reason)
+			raise xmpp.NodeProcessed
+
+		if not transports_ok:
+			# TODO: http://xmpp.org/extensions/xep-0166.html#session-terminate
+			reason = xmpp.Node('reason')
+			reason.setTag('unsupported-transports')
+			self.__defaultCB(stanza, jingle, error, action)
+			self._session_terminate(reason)
+			raise xmpp.NodeProcessed
+
+		self.state = JingleStates.pending
+
+		# Send event about starting a session
+		self.connection.dispatch('JINGLE_INCOMING', (self.peerjid, self.sid,
+			contents))
+
+	def __broadcastCB(self, stanza, jingle, error, action):
+		''' Broadcast the stanza contents to proper content handlers. '''
+		for content in jingle.iterTags('content'):
+			name = content['name']
+			creator = content['creator']
+			cn = self.contents[(creator, name)]
+			cn.stanzaCB(stanza, content, error, action)
+
+	def __sessionTerminateCB(self, stanza, jingle, error, action):
+		self.connection.delete_jingle_session(self.peerjid, self.sid)
+		reason, text = self.__reason_from_stanza(jingle)
+		if reason not in ('success', 'cancel', 'decline'):
+			self.__dispatch_error(reason, reason, text)
+		if text:
+			text = '%s (%s)' % (reason, text)
+		else:
+			text = reason#TODO
+		self.connection.dispatch('JINGLE_DISCONNECTED',
+			(self.peerjid, self.sid, None, text))
+
+	def __broadcastAllCB(self, stanza, jingle, error, action):
+		''' Broadcast the stanza to all content handlers. '''
+		for content in self.contents.itervalues():
+			content.stanzaCB(stanza, None, error, action)
+
+	''' Internal methods. '''
+	def __parse_contents(self, jingle):
+		#TODO: Needs some reworking
+		contents = []
+		contents_rejected = []
+		contents_ok = False
+		transports_ok = False
+
+		for element in jingle.iterTags('content'):
+			desc = element.getTag('description')
+			desc_ns = desc.getNamespace()
+			tran_ns = element.getTag('transport').getNamespace()
+			if desc_ns == xmpp.NS_JINGLE_RTP and desc['media'] in ('audio', 'video'):
+				contents_ok = True
+				#TODO: Everything here should be moved somewhere else
+				if tran_ns == xmpp.NS_JINGLE_ICE_UDP:
+					if desc['media'] == 'audio':
+						self.add_content(element['name'], JingleVoIP(self), 'peer')
+					else:
+						self.add_content(element['name'], JingleVideo(self), 'peer')
+					contents.append((desc['media'],))
+					transports_ok = True
+				else:
+					contents_rejected.append((element['name'], 'peer'))
+			else:
+				contents_rejected.append((element['name'], 'peer'))
+
+		return (contents_ok, transports_ok, contents, contents_rejected)
+
+	def __dispatch_error(self, error, jingle_error=None, text=None):
+		if jingle_error:
+			error = jingle_error
+		if text:
+			text = '%s (%s)' % (error, text)
+		else:
+			text = error
+		self.connection.dispatch('JINGLE_ERROR', (self.peerjid, self.sid, text))
+
+	def __reason_from_stanza(self, stanza):
+		reason = 'success'
+		reasons = ['success', 'busy', 'cancel', 'connectivity-error',
+			'decline', 'expired', 'failed-application', 'failed-transport',
+			'general-error', 'gone', 'incompatible-parameters', 'media-error',
+			'security-error', 'timeout', 'unsupported-applications',
+			'unsupported-transports']
+		tag = stanza.getTag('reason')
+		if tag:
+			text = tag.getTagData('text')
+			for r in reasons:
+				if tag.getTag(r):
+					reason = r
+					break
+		return (reason, text)
+
+	''' Methods that make/send proper pieces of XML. They check if the session
+	is in appropriate state. '''
+	def __make_jingle(self, action):
+		stanza = xmpp.Iq(typ='set', to=xmpp.JID(self.peerjid))
+		attrs = {'action': action,
+			'sid': self.sid}
+		if action == 'session-initiate':
+			attrs['initiator'] = self.initiator
+		elif action == 'session-accept':
+			attrs['responder'] = self.responder
+		jingle = stanza.addChild('jingle', attrs=attrs, namespace=xmpp.NS_JINGLE)
+		return stanza, jingle
+
+	def __send_error(self, stanza, error, jingle_error=None, text=None):
+		err = xmpp.Error(stanza, error)
+		err.setNamespace(xmpp.NS_STANZAS)
+		if jingle_error:
+			err.setTag(jingle_error, namespace=xmpp.NS_JINGLE_ERRORS)
+		if text:
+			err.setTagData('text', text)
+		self.connection.connection.send(err)
+		self.__dispatch_error(error, jingle_error, text)
+
+	def __append_content(self, jingle, content):
+		''' Append <content/> element to <jingle/> element,
+		with (full=True) or without (full=False) <content/>
+		children. '''
+		jingle.addChild('content',
+			attrs={'name': content.name, 'creator': content.creator})
+
+	def __append_contents(self, jingle):
+		''' Append all <content/> elements to <jingle/>.'''
+		# TODO: integrate with __appendContent?
+		# TODO: parameters 'name', 'content'?
+		for content in self.contents.values():
+			self.__append_content(jingle, content)
+
+	def __session_initiate(self):
+		assert self.state == JingleStates.ended
+		stanza, jingle = self.__make_jingle('session-initiate')
+		self.__append_contents(jingle)
+		self.__broadcastCB(stanza, jingle, None, 'session-initiate-sent')
+		self.connection.connection.send(stanza)
+		self.state = JingleStates.pending
+
+	def __session_accept(self):
+		assert self.state == JingleStates.pending
+		stanza, jingle = self.__make_jingle('session-accept')
+		self.__append_contents(jingle)
+		self.__broadcastCB(stanza, jingle, None, 'session-accept-sent')
+		self.connection.connection.send(stanza)
+		self.state = JingleStates.active
+
+	def __session_info(self, payload=None):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('session-info')
+		if payload:
+			jingle.addChild(node=payload)
+		self.connection.connection.send(stanza)
+
+	def _session_terminate(self, reason=None):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('session-terminate')
+		if reason is not None:
+			jingle.addChild(node=reason)
+		self.__broadcastAllCB(stanza, jingle, None, 'session-terminate-sent')
+		self.connection.connection.send(stanza)
+		reason, text = self.__reason_from_stanza(jingle)
+		if reason not in ('success', 'cancel', 'decline'):
+			self.__dispatch_error(reason, reason, text)
+		if text:
+			text = '%s (%s)' % (reason, text)
+		else:
+			text = reason
+		self.connection.delete_jingle_session(self.peerjid, self.sid)
+		self.connection.dispatch('JINGLE_DISCONNECTED',
+			(self.peerjid, self.sid, None, text))
+
+	def __content_add(self, content):
+		#TODO: test
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('content-add')
+		self.__append_content(jingle, content)
+		self.__broadcastCB(stanza, jingle, None, 'content-add-sent')
+		self.connection.connection.send(stanza)
+
+	def __content_accept(self, content):
+		#TODO: test
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('content-accept')
+		self.__append_content(jingle, content)
+		self.__broadcastCB(stanza, jingle, None, 'content-accept-sent')
+		self.connection.connection.send(stanza)
+
+	def __content_reject(self, content):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('content-reject')
+		self.__append_content(jingle, content)
+		self.connection.connection.send(stanza)
+		#TODO: this will fail if content is not an RTP content
+		self.connection.dispatch('JINGLE_DISCONNECTED',
+			(self.peerjid, self.sid, content.media, 'rejected'))
+
+	def __content_modify(self):
+		assert self.state != JingleStates.ended
+
+	def __content_remove(self, content):
+		assert self.state != JingleStates.ended
+		stanza, jingle = self.__make_jingle('content-remove')
+		self.__append_content(jingle, content)
+		self.connection.connection.send(stanza)
+		#TODO: this will fail if content is not an RTP content
+		self.connection.dispatch('JINGLE_DISCONNECTED',
+			(self.peerjid, self.sid, content.media, 'removed'))
+
+	def content_negociated(self, media):
+		self.connection.dispatch('JINGLE_CONNECTED', (self.peerjid, self.sid,
+			media))
+
+#TODO:
+#class JingleTransport(object):
+#	''' An abstraction of a transport in Jingle sessions. '''
+#	def __init__(self):
+#		pass
+
+
+class JingleContent(object):
+	''' An abstraction of content in Jingle sessions. '''
+	def __init__(self, session, node=None):
+		self.session = session
+		# will be filled by JingleSession.add_content()
+		# don't uncomment these lines, we will catch more buggy code then
+		# (a JingleContent not added to session shouldn't send anything)
+		#self.creator = None
+		#self.name = None
+		self.accepted = False
+		self.sent = False
+		self.candidates = [] # Local transport candidates
+		self.remote_candidates = [] # Remote transport candidates
+
+		self.senders = 'both' #FIXME
+		self.allow_sending = True # Used for stream direction, attribute 'senders'
+
+		self.callbacks = {
+			# these are called when *we* get stanzas
+			'content-accept': [self.__transportInfoCB],
+			'content-add': [self.__transportInfoCB],
+			'content-modify': [],
+			'content-reject': [],
+			'content-remove': [],
+			'description-info': [],
+			'security-info': [],
+			'session-accept': [self.__transportInfoCB],
+			'session-info': [],
+			'session-initiate': [self.__transportInfoCB],
+			'session-terminate': [],
+			'transport-info': [self.__transportInfoCB],
+			'transport-replace': [],
+			'transport-accept': [],
+			'transport-reject': [],
+			'iq-result': [],
+			'iq-error': [],
+			# these are called when *we* sent these stanzas
+			'content-accept-sent': [self.__fillJingleStanza],
+			'content-add-sent': [self.__fillJingleStanza],
+			'session-initiate-sent': [self.__fillJingleStanza],
+			'session-accept-sent': [self.__fillJingleStanza],
+			'session-terminate-sent': [],
+		}
+
+	def is_ready(self):
+		#print '[%s] %s, %s' % (self.media, self.candidates_ready,
+		#	self.p2psession.get_property('codecs-ready'))
+		return (self.accepted and self.candidates_ready and not self.sent
+			and self.p2psession.get_property('codecs-ready'))
+
+	def stanzaCB(self, stanza, content, error, action):
+		''' Called when something related to our content was sent by peer. '''
+		if action in self.callbacks:
+			for callback in self.callbacks[action]:
+				callback(stanza, content, error, action)
+
+	def __transportInfoCB(self, stanza, content, error, action):
+		''' Got a new transport candidate. '''
+		candidates = []
+		transport = content.getTag('transport')
+		for candidate in transport.iterTags('candidate'):
+			cand = farsight.Candidate()
+			cand.component_id = int(candidate['component'])
+			cand.ip = str(candidate['ip'])
+			cand.port = int(candidate['port'])
+			cand.foundation = str(candidate['foundation'])
+			#cand.type = farsight.CANDIDATE_TYPE_LOCAL
+			cand.priority = int(candidate['priority'])
+
+			if candidate['protocol'] == 'udp':
+				cand.proto = farsight.NETWORK_PROTOCOL_UDP
+			else:
+				# we actually don't handle properly different tcp options in jingle
+				cand.proto = farsight.NETWORK_PROTOCOL_TCP
+
+			cand.username = str(transport['ufrag'])
+			cand.password = str(transport['pwd'])
+
+			#FIXME: huh?
+			types = {'host': farsight.CANDIDATE_TYPE_HOST,
+						'srflx': farsight.CANDIDATE_TYPE_SRFLX,
+						'prflx': farsight.CANDIDATE_TYPE_PRFLX,
+						'relay': farsight.CANDIDATE_TYPE_RELAY,
+						'multicast': farsight.CANDIDATE_TYPE_MULTICAST}
+			if 'type' in candidate and candidate['type'] in types:
+				cand.type = types[candidate['type']]
+			else:
+				print 'Unknown type %s', candidate['type']
+			candidates.append(cand)
+		#FIXME: connectivity should not be etablished yet
+		# Instead, it should be etablished after session-accept!
+		if len(candidates) > 0:
+			if self.sent:
+				self.p2pstream.set_remote_candidates(candidates)
+			else:
+				self.remote_candidates.extend(candidates)
+			#self.p2pstream.set_remote_candidates(candidates)
+			#print self.media, self.creator, self.name, candidates
+
+	def __content(self, payload=[]):
+		''' Build a XML content-wrapper for our data. '''
+		return xmpp.Node('content',
+			attrs={'name': self.name, 'creator': self.creator},
+			payload=payload)
+
+	def __candidate(self, candidate):
+		types = {farsight.CANDIDATE_TYPE_HOST: 'host',
+			farsight.CANDIDATE_TYPE_SRFLX: 'srflx',
+			farsight.CANDIDATE_TYPE_PRFLX: 'prflx',
+			farsight.CANDIDATE_TYPE_RELAY: 'relay',
+			farsight.CANDIDATE_TYPE_MULTICAST: 'multicast'}
+		attrs = {
+			'component': candidate.component_id,
+			'foundation': '1', # hack
+			'generation': '0',
+			'ip': candidate.ip,
+			'network': '0',
+			'port': candidate.port,
+			'priority': int(candidate.priority), # hack
+		}
+		if candidate.type in types:
+			attrs['type'] = types[candidate.type]
+		if candidate.proto == farsight.NETWORK_PROTOCOL_UDP:
+			attrs['protocol'] = 'udp'
+		else:
+			# we actually don't handle properly different tcp options in jingle
+			attrs['protocol'] = 'tcp'
+		return xmpp.Node('candidate', attrs=attrs)
+
+	def iter_candidates(self):
+		for candidate in self.candidates:
+			yield self.__candidate(candidate)
+
+	def send_candidate(self, candidate):
+		content = self.__content()
+		transport = content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport')
+
+		if candidate.username and candidate.password:
+			transport['ufrag'] = candidate.username
+			transport['pwd'] = candidate.password
+
+		transport.addChild(node=self.__candidate(candidate))
+		self.session.send_transport_info(content)
+
+	def __fillJingleStanza(self, stanza, content, error, action):
+		''' Add our things to session-initiate stanza. '''
+		self._fillContent(content)
+
+		self.sent = True
+
+		if self.candidates and self.candidates[0].username and \
+		self.candidates[0].password:
+			attrs = {'ufrag': self.candidates[0].username,
+				'pwd': self.candidates[0].password}
+		else:
+			attrs = {}
+		content.addChild(xmpp.NS_JINGLE_ICE_UDP + ' transport', attrs=attrs,
+			payload=self.iter_candidates())
+
+	def destroy(self):
+		self.callbacks = None
+		del self.session.contents[(self.creator, self.name)]
+
+
+class JingleRTPContent(JingleContent):
+	def __init__(self, session, media, node=None):
+		JingleContent.__init__(self, session, node)
+		self.media = media
+		self.farsight_media = {'audio': farsight.MEDIA_TYPE_AUDIO,
+								'video': farsight.MEDIA_TYPE_VIDEO}[media]
+		self.got_codecs = False
+
+		self.candidates_ready = False # True when local candidates are prepared
+
+		self.callbacks['session-initiate'] += [self.__getRemoteCodecsCB]
+		self.callbacks['content-add'] += [self.__getRemoteCodecsCB]
+		self.callbacks['content-accept'] += [self.__getRemoteCodecsCB,
+			self.__contentAcceptCB]
+		self.callbacks['session-accept'] += [self.__getRemoteCodecsCB,
+			self.__contentAcceptCB]
+		self.callbacks['session-accept-sent'] += [self.__contentAcceptCB]
+		self.callbacks['content-accept-sent'] += [self.__contentAcceptCB]
+		self.callbacks['session-terminate'] += [self.__stop]
+		self.callbacks['session-terminate-sent'] += [self.__stop]
+
+	def setup_stream(self):
+		# pipeline and bus
+		self.pipeline = gst.Pipeline()
+		bus = self.pipeline.get_bus()
+		bus.add_signal_watch()
+		bus.connect('message', self._on_gst_message)
+
+		# conference
+		self.conference = gst.element_factory_make('fsrtpconference')
+		self.conference.set_property("sdes-cname", self.session.ourjid)
+		self.pipeline.add(self.conference)
+		self.funnel = None
+
+		self.p2psession = self.conference.new_session(self.farsight_media)
+
+		participant = self.conference.new_participant(self.session.peerjid)
+		#FIXME: Consider a workaround, here... 
+		# pidgin and telepathy-gabble don't follow the XEP, and it won't work
+		# due to bad controlling-mode
+		params = {'controlling-mode': self.session.weinitiate,# 'debug': False}
+			'stun-ip': '69.0.208.27', 'debug': False}
+
+		self.p2pstream = self.p2psession.new_stream(participant,
+			farsight.DIRECTION_RECV, 'nice', params)
+
+	def _fillContent(self, content):
+		content.addChild(xmpp.NS_JINGLE_RTP + ' description',
+			attrs={'media': self.media}, payload=self.iter_codecs())
+
+	def _setup_funnel(self):
+		self.funnel = gst.element_factory_make('fsfunnel')
+		self.pipeline.add(self.funnel)
+		self.funnel.set_state(gst.STATE_PLAYING)
+		self.sink.set_state(gst.STATE_PLAYING)
+		self.funnel.link(self.sink)
+
+	def _on_src_pad_added(self, stream, pad, codec):
+		if not self.funnel:
+			self._setup_funnel()
+		pad.link(self.funnel.get_pad('sink%d'))
+
+	def _on_gst_message(self, bus, message):
+		if message.type == gst.MESSAGE_ELEMENT:
+			name = message.structure.get_name()
+			if name == 'farsight-new-active-candidate-pair':
+				pass
+			elif name == 'farsight-recv-codecs-changed':
+				pass
+			elif name == 'farsight-codecs-changed':
+				if self.is_ready():
+					self.session.on_session_state_changed(self)
+				#TODO: description-info
+			elif name == 'farsight-local-candidates-prepared':
+				self.candidates_ready = True
+				if self.is_ready():
+					self.session.on_session_state_changed(self)
+			elif name == 'farsight-new-local-candidate':
+				candidate = message.structure['candidate']
+				self.candidates.append(candidate)
+				if self.candidates_ready:
+					#FIXME: Is this case even possible?
+					self.send_candidate(candidate)
+			elif name == 'farsight-component-state-changed':
+				state = message.structure['state']
+				print message.structure['component'], state
+				if state == farsight.STREAM_STATE_FAILED:
+					reason = xmpp.Node('reason')
+					reason.setTag('failed-transport')
+					self.session._session_terminate(reason)
+			elif name == 'farsight-error':
+				print 'Farsight error #%d!' % message.structure['error-no']
+				print 'Message: %s' % message.structure['error-msg']
+				print 'Debug: %s' % message.structure['debug-msg']
+			else:
+				print name
+
+	def __contentAcceptCB(self, stanza, content, error, action):
+		if self.accepted:
+			if len(self.remote_candidates) > 0:
+				self.p2pstream.set_remote_candidates(self.remote_candidates)
+				self.remote_candidates = []
+			#TODO: farsight.DIRECTION_BOTH only if senders='both'
+			self.p2pstream.set_property('direction', farsight.DIRECTION_BOTH)
+			self.session.content_negociated(self.media)
+
+	def __getRemoteCodecsCB(self, stanza, content, error, action):
+		''' Get peer codecs from what we get from peer. '''
+		if self.got_codecs:
+			return
+
+		codecs = []
+		for codec in content.getTag('description').iterTags('payload-type'):
+			c = farsight.Codec(int(codec['id']), codec['name'],
+				self.farsight_media, int(codec['clockrate']))
+			if 'channels' in codec:
+				c.channels = int(codec['channels'])
+			else:
+				c.channels = 1
+			c.optional_params = [(str(p['name']), str(p['value'])) for p in \
+				codec.iterTags('parameter')]
+			codecs.append(c)
+
+		if len(codecs) > 0:
+			#FIXME: Handle this case:
+			# glib.GError: There was no intersection between the remote codecs and
+			# the local ones
+			self.p2pstream.set_remote_codecs(codecs)
+			self.got_codecs = True
+
+	def iter_codecs(self):
+		codecs = self.p2psession.get_property('codecs')
+		for codec in codecs:
+			attrs = {'name': codec.encoding_name,
+				'id': codec.id,
+				'channels': codec.channels}
+			if codec.clock_rate:
+				attrs['clockrate'] = codec.clock_rate
+			if codec.optional_params:
+				payload = (xmpp.Node('parameter', {'name': name, 'value': value})
+					for name, value in codec.optional_params)
+			else:	payload = ()
+			yield xmpp.Node('payload-type', attrs, payload)
+
+	def __stop(self, *things):
+		self.pipeline.set_state(gst.STATE_NULL)
+
+	def __del__(self):
+		self.__stop()
+
+	def destroy(self):
+		JingleContent.destroy(self)
+		self.p2pstream.disconnect_by_func(self._on_src_pad_added)
+		self.pipeline.get_bus().disconnect_by_func(self._on_gst_message)
+
+
+class JingleVoIP(JingleRTPContent):
+	''' Jingle VoIP sessions consist of audio content transported
+	over an ICE UDP protocol. '''
+	def __init__(self, session, node=None):
+		JingleRTPContent.__init__(self, session, 'audio', node)
+		self.setup_stream()
+
+
+	''' Things to control the gstreamer's pipeline '''
+	def setup_stream(self):
+		JingleRTPContent.setup_stream(self)
+
+		# Configure SPEEX
+		# Workaround for psi (not needed since rev
+		# 147aedcea39b43402fe64c533d1866a25449888a):
+		#  place 16kHz before 8kHz, as buggy psi versions will take in
+		#  account only the first codec
+
+		codecs = [farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
+			farsight.MEDIA_TYPE_AUDIO, 16000),
+			farsight.Codec(farsight.CODEC_ID_ANY, 'SPEEX',
+			farsight.MEDIA_TYPE_AUDIO, 8000)]
+		self.p2psession.set_codec_preferences(codecs)
+
+		# the local parts
+		# TODO: use gconfaudiosink?
+		# sink = get_first_gst_element(['alsasink', 'osssink', 'autoaudiosink'])
+		self.sink = gst.element_factory_make('alsasink')
+		self.sink.set_property('sync', False)
+		#sink.set_property('latency-time', 20000)
+		#sink.set_property('buffer-time', 80000)
+
+		# TODO: use gconfaudiosrc?
+		src_mic = gst.element_factory_make('alsasrc')
+		src_mic.set_property('blocksize', 320)
+
+		self.mic_volume = gst.element_factory_make('volume')
+		self.mic_volume.set_property('volume', 1)
+
+		# link gst elements
+		self.pipeline.add(self.sink, src_mic, self.mic_volume)
+		src_mic.link(self.mic_volume)
+
+		self.mic_volume.get_pad('src').link(self.p2psession.get_property(
+			'sink-pad'))
+		self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
+
+		# The following is needed for farsight to process ICE requests:
+		self.pipeline.set_state(gst.STATE_PLAYING)
+
+
+class JingleVideo(JingleRTPContent):
+	def __init__(self, session, node=None):
+		JingleRTPContent.__init__(self, session, 'video', node)
+		self.setup_stream()
+
+	''' Things to control the gstreamer's pipeline '''
+	def setup_stream(self):
+		#TODO: Everything is not working properly:
+		# sometimes, one window won't show up,
+		# sometimes it'll freeze...
+		JingleRTPContent.setup_stream(self)
+		# the local parts
+		src_vid = gst.element_factory_make('videotestsrc')
+		src_vid.set_property('is-live', True)
+		videoscale = gst.element_factory_make('videoscale')
+		caps = gst.element_factory_make('capsfilter')
+		caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
+		colorspace = gst.element_factory_make('ffmpegcolorspace')
+
+		self.pipeline.add(src_vid, videoscale, caps, colorspace)
+		gst.element_link_many(src_vid, videoscale, caps, colorspace)
+
+		self.sink = gst.element_factory_make('xvimagesink')
+		self.pipeline.add(self.sink)
+
+		colorspace.get_pad('src').link(self.p2psession.get_property('sink-pad'))
+		self.p2pstream.connect('src-pad-added', self._on_src_pad_added)
+
+		# The following is needed for farsight to process ICE requests:
+		self.pipeline.set_state(gst.STATE_PLAYING)
+
+
+class ConnectionJingle(object):
+	''' This object depends on that it is a part of Connection class. '''
+	def __init__(self):
+		# dictionary: (jid, sessionid) => JingleSession object
+		self.__sessions = {}
+
+		# dictionary: (jid, iq stanza id) => JingleSession object,
+		# one time callbacks
+		self.__iq_responses = {}
+
+	def add_jingle(self, jingle):
+		''' Add a jingle session to a jingle stanza dispatcher
+		jingle - a JingleSession object.
+		'''
+		self.__sessions[(jingle.peerjid, jingle.sid)] = jingle
+
+	def delete_jingle_session(self, peerjid, sid):
+		''' Remove a jingle session from a jingle stanza dispatcher '''
+		key = (peerjid, sid)
+		if key in self.__sessions:
+			#FIXME: Move this elsewhere?
+			for content in self.__sessions[key].contents.values():
+				content.destroy()
+			self.__sessions[key].callbacks = []
+			del self.__sessions[key]
+
+	def _JingleCB(self, con, stanza):
+		''' The jingle stanza dispatcher.
+		Route jingle stanza to proper JingleSession object,
+		or create one if it is a new session.
+		TODO: Also check if the stanza isn't an error stanza, if so
+		route it adequatelly.'''
+
+		# get data
+		jid = helpers.get_full_jid_from_iq(stanza)
+		id = stanza.getID()
+
+		if (jid, id) in self.__iq_responses.keys():
+			self.__iq_responses[(jid, id)].stanzaCB(stanza)
+			del self.__iq_responses[(jid, id)]
+			raise xmpp.NodeProcessed
+
+		jingle = stanza.getTag('jingle')
+		if not jingle: return
+		sid = jingle.getAttr('sid')
+
+		# do we need to create a new jingle object
+		if (jid, sid) not in self.__sessions:
+			#TODO: tie-breaking and other things...
+			newjingle = JingleSession(con=self, weinitiate=False, jid=jid, sid=sid)
+			self.add_jingle(newjingle)
+
+		# we already have such session in dispatcher...
+		self.__sessions[(jid, sid)].stanzaCB(stanza)
+
+		raise xmpp.NodeProcessed
+
+	def startVoIP(self, jid):
+		if self.get_jingle_session(jid, media='audio'):
+			return self.get_jingle_session(jid, media='audio').sid
+		jingle = self.get_jingle_session(jid, media='video')
+		if jingle:
+			jingle.add_content('voice', JingleVoIP(jingle))
+		else:
+			jingle = JingleSession(self, weinitiate=True, jid=jid)
+			self.add_jingle(jingle)
+			jingle.add_content('voice', JingleVoIP(jingle))
+			jingle.start_session()
+		return jingle.sid
+
+	def startVideoIP(self, jid):
+		if self.get_jingle_session(jid, media='video'):
+			return self.get_jingle_session(jid, media='video').sid
+		jingle = self.get_jingle_session(jid, media='audio')
+		if jingle:
+			jingle.add_content('video', JingleVideo(jingle))
+		else:
+			jingle = JingleSession(self, weinitiate=True, jid=jid)
+			self.add_jingle(jingle)
+			jingle.add_content('video', JingleVideo(jingle))
+			jingle.start_session()
+		return jingle.sid
+
+	def get_jingle_session(self, jid, sid=None, media=None):
+		if sid:
+			if (jid, sid) in self.__sessions:
+				return self.__sessions[(jid, sid)]
+			else:
+				return None
+		elif media:
+			if media not in ('audio', 'video'):
+				return None
+			for session in self.__sessions.values():
+				if session.peerjid == jid and session.get_content(media):
+					return session
+
+		return None
diff --git a/src/common/meta.py b/src/common/meta.py
new file mode 100644
index 0000000000000000000000000000000000000000..153c2e952283bab6b036dd4ea2631f1e326d7e6d
--- /dev/null
+++ b/src/common/meta.py
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+
+import types
+
+class VerboseClassType(type):
+	indent = ''
+
+	def __init__(cls, name, bases, dict):
+		super(VerboseClassType, cls).__init__(cls, name, bases, dict)
+		new = {}
+		print 'Initializing new class %s:' % cls
+		for fname, fun in dict.iteritems():
+			wrap = hasattr(fun, '__call__')
+			print '%s%s is %s, we %s wrap it.' % \
+				(cls.__class__.indent, fname, fun, wrap and 'will' or "won't")
+			if not wrap: continue
+			setattr(cls, fname, cls.wrap(name, fname, fun))
+
+	def wrap(cls, name, fname, fun):
+		def verbose(*a, **b):
+			args = ', '.join(map(repr, a)+map(lambda x:'%s=%r'%x, b.iteritems()))
+			print '%s%s.%s(%s):' % (cls.__class__.indent, name, fname, args)
+			cls.__class__.indent += '|   '
+			r = fun(*a, **b)
+			cls.__class__.indent = cls.__class__.indent[:-4]
+			print '%s+=%r' % (cls.__class__.indent, r)
+			return r
+		verbose.__name__ = fname
+		return verbose
+
+def nested_property(f):
+	ret = f()
+	p = {}
+	for v in ('fget', 'fset', 'fdel', 'doc'):
+		if v in ret: p[v]=ret[v]
+	return property(**p)
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index 3d0debca588ec0306e6ce723af109ad204a37757..6197714098d959d5b36b6763c759a89dc51bae07 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -63,6 +63,13 @@ NS_HTTP_BIND	='http://jabber.org/protocol/httpbind'				  # XEP-0124
 NS_IBB		  ='http://jabber.org/protocol/ibb'
 NS_INVISIBLE	='presence-invisible'								   # Jabberd2
 NS_IQ		   ='iq'												   # Jabberd2
+NS_JINGLE       ='urn:xmpp:jingle:1'                                    # XEP-0166
+NS_JINGLE_ERRORS='urn:xmpp:jingle:errors:1'                             # XEP-0166
+NS_JINGLE_RTP   ='urn:xmpp:jingle:apps:rtp:1'                           # XEP-0167
+NS_JINGLE_RTP_AUDIO='urn:xmpp:jingle:apps:rtp:audio'                    # XEP-0167
+NS_JINGLE_RTP_VIDEO='urn:xmpp:jingle:apps:rtp:video'                    # XEP-0167
+NS_JINGLE_RAW_UDP='urn:xmpp:jingle:transports:raw-udp:1'                # XEP-0177
+NS_JINGLE_ICE_UDP='urn:xmpp:jingle:transports:ice-udp:1'                # XEP-0176
 NS_LAST		 ='jabber:iq:last'
 NS_MESSAGE	  ='message'											  # Jabberd2
 NS_MOOD		 ='http://jabber.org/protocol/mood'					  # XEP-0107
diff --git a/src/common/xmpp/simplexml.py b/src/common/xmpp/simplexml.py
index a658189024479b368432682e770669ae4d9278c3..f47142c46ece339a5242146b33aabca7710a2e6d 100644
--- a/src/common/xmpp/simplexml.py
+++ b/src/common/xmpp/simplexml.py
@@ -297,6 +297,9 @@ class Node(object):
 	def __delitem__(self,item):
 		''' Deletes node's attribute "item". '''
 		return self.delAttr(item)
+	def __contains__(self,item):
+		""" Checks if node has attribute "item" """
+		return self.has_attr(item)
 	def __getattr__(self,attr):
 		''' Reduce memory usage caused by T/NT classes - use memory only when needed. '''
 		if attr=='T':
diff --git a/src/dialogs.py b/src/dialogs.py
index f4927ce18534222460c3762bd5c7ab1e1065389a..a0fc9f9b7d39175f209c957015bf02051fd9428b 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -32,6 +32,7 @@
 import gtk
 import gobject
 import os
+from weakref import WeakValueDictionary
 
 import gtkgui_helpers
 import vcard
@@ -4514,6 +4515,8 @@ class GPGInfoWindow:
 	def on_close_button_clicked(self, widget):
 		self.window.destroy()
 
+
+
 class ResourceConflictDialog(TimeoutDialog, InputDialog):
 	def __init__(self, title, text, resource, ok_handler):
 		TimeoutDialog.__init__(self, 15, self.on_timeout)
@@ -4525,4 +4528,106 @@ class ResourceConflictDialog(TimeoutDialog, InputDialog):
 	def on_timeout(self):
 		self.on_okbutton_clicked(None)
 
+
+
+class VoIPCallReceivedDialog(object):
+	instances = {}
+	def __init__(self, account, contact_jid, sid, content_types):
+		self.instances[(contact_jid, sid)] = self
+		self.account = account
+		self.fjid = contact_jid
+		self.sid = sid
+		self.content_types = content_types
+
+		xml = gtkgui_helpers.get_glade('voip_call_received_dialog.glade')
+		xml.signal_autoconnect(self)
+
+		jid = gajim.get_jid_without_resource(self.fjid)
+		contact = gajim.contacts.get_first_contact_from_jid(account, jid)
+		if contact and contact.name:
+			self.contact_text = '%s (%s)' % (contact.name, jid)
+		else:
+			self.contact_text = contact_jid
+
+		self.dialog = xml.get_widget('voip_call_received_messagedialog')
+		self.set_secondary_text()
+
+		self.dialog.show_all()
+
+	@classmethod
+	def get_dialog(cls, jid, sid):
+		if (jid, sid) in cls.instances:
+			return cls.instances[(jid, sid)]
+		else:
+			return None
+
+	def set_secondary_text(self):
+		if 'audio' in self.content_types and 'video' in self.content_types:
+			types_text = _('an audio and video')
+		elif 'audio' in self.content_types:
+			types_text = _('an audio')
+		elif 'video' in self.content_types:
+			types_text = _('a video')
+
+		# do the substitution
+		self.dialog.set_property('secondary-text',
+			_('%(contact)s wants to start %(type)s session with you. Do you want '
+			'to answer the call?') % {'contact': self.contact_text, 'type': types_text})
+
+	def add_contents(self, content_types):
+		for type_ in content_types:
+			if type_ not in self.content_types:
+				self.content_types.add(type_)
+		self.set_secondary_text()
+
+	def on_voip_call_received_messagedialog_destroy(self, dialog):
+		if (self.fjid, self.sid) in self.instances:
+			del self.instances[(self.fjid, self.sid)]
+
+	def on_voip_call_received_messagedialog_close(self, dialog):
+		return self.on_voip_call_received_messagedialog_response(dialog,
+			gtk.RESPONSE_NO)
+
+	def on_voip_call_received_messagedialog_response(self, dialog, response):
+		# we've got response from user, either stop connecting or accept the call
+		session = gajim.connections[self.account].get_jingle_session(self.fjid,
+			self.sid)
+		if not session:
+			return
+		if response == gtk.RESPONSE_YES:
+			#TODO: Ensure that ctrl.contact.resource == resource
+			jid = gajim.get_jid_without_resource(self.fjid)
+			resource = gajim.get_resource_from_jid(self.fjid)
+			ctrl = gajim.interface.msg_win_mgr.get_control(self.fjid, self.account)
+			if not ctrl:
+				ctrl = gajim.interface.msg_win_mgr.get_control(jid, self.account)
+			if not ctrl:
+				# open chat control
+				contact = gajim.contacts.get_contact(self.account, jid, resource)
+				if not contact:
+					contact = gajim.contacts.get_contact(self.account, jid)
+				if not contact:
+					return
+				ctrl = gajim.interface.new_chat(contact, self.account)
+			# Chat control opened, update content's status
+			if session.get_content('audio'):
+				ctrl.set_audio_state('connecting', self.sid)
+			if session.get_content('video'):
+				ctrl.set_video_state('connecting', self.sid)
+			# Now, accept the content/sessions.
+			# This should be done after the chat control is running
+			if not session.accepted:
+				session.approve_session()
+			for content in self.content_types:
+				session.approve_content(content)
+		else: # response==gtk.RESPONSE_NO
+			if not session.accepted:
+				session.decline_session()
+			else:
+				for content in self.content_types:
+					session.reject_content(content)
+
+		dialog.destroy()
+
+
 # vim: se ts=3:
diff --git a/src/features_window.py b/src/features_window.py
index ebe00155808d24ca5142ebf044968044b0129fe2..609959e65539970281109c34caf5620e25b893f6 100644
--- a/src/features_window.py
+++ b/src/features_window.py
@@ -107,6 +107,10 @@ class FeaturesWindow:
 				_('Ability to have clickable URLs in chat and groupchat window banners.'),
 				_('Requires python-sexy.'),
 				_('Requires python-sexy.')),
+			_('Audio / Video'): (self.farsight_available,
+				_('Ability to start audio and video chat.'),
+				_('Requires python-farsight.'),
+				_('Feature not available under Windows.')),
 		}
 
 		# name, supported
@@ -265,4 +269,7 @@ class FeaturesWindow:
 	def pysexy_available(self):
 		return gajim.HAVE_PYSEXY
 
+	def farsight_available(self):
+		return gajim.HAVE_FARSIGHT
+
 # vim: se ts=3:
diff --git a/src/gajim.py b/src/gajim.py
index 3dd191d37776e33b3918268e89ecd412acddff07..9f17ae7ae0a8c45f7b1d394d23bbac621a76f65a 100644
--- a/src/gajim.py
+++ b/src/gajim.py
@@ -2107,6 +2107,102 @@ class Interface:
 			_('You are already connected to this account with the same resource. '
 			'Please type a new one'), resource=proposed_resource, ok_handler=on_ok)
 
+	def handle_event_jingle_incoming(self, account, data):
+		# ('JINGLE_INCOMING', account, peer jid, sid, tuple-of-contents==(type,
+		# data...))
+		# TODO: conditional blocking if peer is not in roster
+
+		# unpack data
+		peerjid, sid, contents = data
+		content_types = set(c[0] for c in contents)
+
+		# check type of jingle session
+		if 'audio' in content_types or 'video' in content_types:
+			# a voip session...
+			# we now handle only voip, so the only thing we will do here is
+			# not to return from function
+			pass
+		else:
+			# unknown session type... it should be declined in common/jingle.py
+			return
+
+		jid = gajim.get_jid_without_resource(peerjid)
+		resource = gajim.get_resource_from_jid(peerjid)
+		ctrl = self.msg_win_mgr.get_control(peerjid, account)
+		if not ctrl:
+			ctrl = self.msg_win_mgr.get_control(jid, account)
+		if ctrl:
+			if 'audio' in content_types:
+				ctrl.set_audio_state('connection_received', sid)
+			if 'video' in content_types:
+				ctrl.set_video_state('connection_received', sid)
+
+		dlg = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
+		if dlg:
+			dlg.add_contents(content_types)
+			return
+
+		if helpers.allow_popup_window(account):
+			dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
+			return
+
+		self.add_event(account, peerjid, 'jingle-incoming', (peerjid, sid,
+			content_types))
+
+		if helpers.allow_showing_notification(account):
+			# TODO: we should use another pixmap ;-)
+			img = os.path.join(gajim.DATA_DIR, 'pixmaps', 'events',
+				'ft_request.png')
+			txt = _('%s wants to start a voice chat.') % gajim.get_name_from_jid(
+				account, peerjid)
+			path = gtkgui_helpers.get_path_to_generic_or_avatar(img)
+			event_type = _('Voice Chat Request')
+			notify.popup(event_type, peerjid, account, 'jingle-incoming',
+				path_to_image = path, title = event_type, text = txt)
+
+	def handle_event_jingle_connected(self, account, data):
+		# ('JINGLE_CONNECTED', account, (peerjid, sid, media))
+		peerjid, sid, media = data
+		if media in ('audio', 'video'):
+			jid = gajim.get_jid_without_resource(peerjid)
+			resource = gajim.get_resource_from_jid(peerjid)
+			ctrl = self.msg_win_mgr.get_control(peerjid, account)
+			if not ctrl:
+				ctrl = self.msg_win_mgr.get_control(jid, account)
+			if ctrl:
+				if media == 'audio':
+					ctrl.set_audio_state('connected', sid)
+				else:
+					ctrl.set_video_state('connected', sid)
+
+	def handle_event_jingle_disconnected(self, account, data):
+		# ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason))
+		peerjid, sid, media, reason = data
+		jid = gajim.get_jid_without_resource(peerjid)
+		resource = gajim.get_resource_from_jid(peerjid)
+		ctrl = self.msg_win_mgr.get_control(peerjid, account)
+		if not ctrl:
+			ctrl = self.msg_win_mgr.get_control(jid, account)
+		if ctrl:
+			if media in ('audio', None):
+				ctrl.set_audio_state('stop', sid=sid, reason=reason)
+			if media in ('video', None):
+				ctrl.set_video_state('stop', sid=sid, reason=reason)
+		dialog = dialogs.VoIPCallReceivedDialog.get_dialog(peerjid, sid)
+		if dialog:
+			dialog.dialog.destroy()
+
+	def handle_event_jingle_error(self, account, data):
+		# ('JINGLE_ERROR', account, (peerjid, sid, reason))
+		peerjid, sid, reason = data
+		jid = gajim.get_jid_without_resource(peerjid)
+		resource = gajim.get_resource_from_jid(peerjid)
+		ctrl = self.msg_win_mgr.get_control(peerjid, account)
+		if not ctrl:
+			ctrl = self.msg_win_mgr.get_control(jid, account)
+		if ctrl:
+			ctrl.set_audio_state('error', reason=reason)
+
 	def handle_event_pep_config(self, account, data):
 		# ('PEP_CONFIG', account, (node, form))
 		if 'pep_services' in self.instances[account]:
@@ -2364,6 +2460,10 @@ class Interface:
 			'INSECURE_SSL_CONNECTION': self.handle_event_insecure_ssl_connection,
 			'PUBSUB_NODE_REMOVED': self.handle_event_pubsub_node_removed,
 			'PUBSUB_NODE_NOT_REMOVED': self.handle_event_pubsub_node_not_removed,
+			'JINGLE_INCOMING': self.handle_event_jingle_incoming,
+			'JINGLE_CONNECTED': self.handle_event_jingle_connected,
+			'JINGLE_DISCONNECTED': self.handle_event_jingle_disconnected,
+			'JINGLE_ERROR': self.handle_event_jingle_error,
 		}
 	
 	def dispatch(self, event, account, data):
@@ -2388,7 +2488,7 @@ class Interface:
 		jid = gajim.get_jid_without_resource(jid)
 		no_queue = len(gajim.events.get_events(account, jid)) == 0
 		# type_ can be gc-invitation file-send-error file-error file-request-error
-		# file-request file-completed file-stopped
+		# file-request file-completed file-stopped jingle-incoming
 		# event_type can be in advancedNotificationWindow.events_list
 		event_types = {'file-request': 'ft_request',
 			'file-completed': 'ft_finished'}
@@ -2548,6 +2648,11 @@ class Interface:
 			self.show_unsubscribed_dialog(account, contact)
 			gajim.events.remove_events(account, jid, event)
 			self.roster.draw_contact(jid, account)
+		elif type_ == 'jingle-incoming':
+ 			event = gajim.events.get_first_event(account, jid, type_)
+ 			peerjid, sid, content_types = event.parameters
+ 			dialogs.VoIPCallReceivedDialog(account, peerjid, sid, content_types)
+ 			gajim.events.remove_events(account, jid, event)
 		if w:
 			w.set_active_tab(ctrl)
 			w.window.window.focus(gtk.get_current_event_time())