start havine audio / video. Doesn't work correctly yet
This commit is contained in:
		
							parent
							
								
									659c5d2670
								
							
						
					
					
						commit
						e746ca9b1a
					
				
					 11 changed files with 250 additions and 151 deletions
				
			
		| 
						 | 
					@ -17,10 +17,9 @@
 | 
				
			||||||
    <property name="can_focus">True</property>
 | 
					    <property name="can_focus">True</property>
 | 
				
			||||||
    <property name="spacing">1</property>
 | 
					    <property name="spacing">1</property>
 | 
				
			||||||
    <child>
 | 
					    <child>
 | 
				
			||||||
      <object class="GtkVBox" id="chat_control_vbox">
 | 
					      <object class="GtkPaned" id="paned1">
 | 
				
			||||||
 | 
					        <property name="visible">True</property>
 | 
				
			||||||
        <property name="can_focus">True</property>
 | 
					        <property name="can_focus">True</property>
 | 
				
			||||||
        <property name="border_width">3</property>
 | 
					 | 
				
			||||||
        <property name="spacing">1</property>
 | 
					 | 
				
			||||||
        <child>
 | 
					        <child>
 | 
				
			||||||
          <object class="GtkVBox" id="vbox2">
 | 
					          <object class="GtkVBox" id="vbox2">
 | 
				
			||||||
            <property name="visible">True</property>
 | 
					            <property name="visible">True</property>
 | 
				
			||||||
| 
						 | 
					@ -329,6 +328,7 @@
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Show a list of emoticons (Alt+M)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Show a list of emoticons (Alt+M)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <property name="focus_on_click">False</property>
 | 
					                    <property name="focus_on_click">False</property>
 | 
				
			||||||
| 
						 | 
					@ -391,6 +391,7 @@
 | 
				
			||||||
                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</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="no_show_all">True</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Add this contact to roster (Ctrl+D)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Add this contact to roster (Ctrl+D)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Add this contact to roster (Ctrl+D)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <child>
 | 
					                    <child>
 | 
				
			||||||
| 
						 | 
					@ -481,6 +482,7 @@
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Invite contacts to the conversation (Ctrl+G)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <property name="focus_on_click">False</property>
 | 
					                    <property name="focus_on_click">False</property>
 | 
				
			||||||
| 
						 | 
					@ -507,6 +509,7 @@
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Show the contact&apos;s profile (Ctrl+I)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Show the contact's profile (Ctrl+I)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Show the contact's profile (Ctrl+I)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <property name="focus_on_click">False</property>
 | 
					                    <property name="focus_on_click">False</property>
 | 
				
			||||||
| 
						 | 
					@ -533,6 +536,7 @@
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Browse the chat history (Ctrl+H)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Browse the chat history (Ctrl+H)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Browse the chat history (Ctrl+H)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <property name="focus_on_click">False</property>
 | 
					                    <property name="focus_on_click">False</property>
 | 
				
			||||||
| 
						 | 
					@ -540,7 +544,6 @@
 | 
				
			||||||
                      <object class="GtkImage" id="history_image">
 | 
					                      <object class="GtkImage" id="history_image">
 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                        <property name="can_focus">False</property>
 | 
					                        <property name="can_focus">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="stock">gtk-justify-fill</property>
 | 
					                        <property name="stock">gtk-justify-fill</property>
 | 
				
			||||||
                        <property name="icon-size">1</property>
 | 
					                        <property name="icon-size">1</property>
 | 
				
			||||||
                      </object>
 | 
					                      </object>
 | 
				
			||||||
| 
						 | 
					@ -571,6 +574,7 @@
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">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="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
 | 
				
			||||||
                    <property name="has_tooltip">True</property>
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Show advanced functions (Alt+D)</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Show advanced functions (Alt+D)</property>
 | 
				
			||||||
                    <property name="relief">none</property>
 | 
					                    <property name="relief">none</property>
 | 
				
			||||||
                    <property name="focus_on_click">False</property>
 | 
					                    <property name="focus_on_click">False</property>
 | 
				
			||||||
| 
						 | 
					@ -610,6 +614,7 @@
 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                        <property name="can_focus">True</property>
 | 
					                        <property name="can_focus">True</property>
 | 
				
			||||||
                        <property name="receives_default">True</property>
 | 
					                        <property name="receives_default">True</property>
 | 
				
			||||||
 | 
					                        <property name="has_tooltip">True</property>
 | 
				
			||||||
                        <property name="relief">none</property>
 | 
					                        <property name="relief">none</property>
 | 
				
			||||||
                        <property name="focus_on_click">False</property>
 | 
					                        <property name="focus_on_click">False</property>
 | 
				
			||||||
                        <property name="orientation">vertical</property>
 | 
					                        <property name="orientation">vertical</property>
 | 
				
			||||||
| 
						 | 
					@ -631,6 +636,7 @@ audio-volume-medium</property>
 | 
				
			||||||
                        <property name="visible">True</property>
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
                        <property name="can_focus">True</property>
 | 
					                        <property name="can_focus">True</property>
 | 
				
			||||||
                        <property name="receives_default">True</property>
 | 
					                        <property name="receives_default">True</property>
 | 
				
			||||||
 | 
					                        <property name="has_tooltip">True</property>
 | 
				
			||||||
                        <property name="relief">none</property>
 | 
					                        <property name="relief">none</property>
 | 
				
			||||||
                        <property name="focus_on_click">False</property>
 | 
					                        <property name="focus_on_click">False</property>
 | 
				
			||||||
                        <property name="yalign">0.4699999988079071</property>
 | 
					                        <property name="yalign">0.4699999988079071</property>
 | 
				
			||||||
| 
						 | 
					@ -701,6 +707,8 @@ audio-mic-volume-low</property>
 | 
				
			||||||
                    <property name="visible">True</property>
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
                    <property name="can_focus">True</property>
 | 
					                    <property name="can_focus">True</property>
 | 
				
			||||||
                    <property name="receives_default">True</property>
 | 
					                    <property name="receives_default">True</property>
 | 
				
			||||||
 | 
					                    <property name="has_tooltip">True</property>
 | 
				
			||||||
 | 
					                    <property name="tooltip_markup" translatable="yes">Send message</property>
 | 
				
			||||||
                    <property name="tooltip_text" translatable="yes">Send message</property>
 | 
					                    <property name="tooltip_text" translatable="yes">Send message</property>
 | 
				
			||||||
                    <property name="image">image4</property>
 | 
					                    <property name="image">image4</property>
 | 
				
			||||||
                    <property name="use_underline">True</property>
 | 
					                    <property name="use_underline">True</property>
 | 
				
			||||||
| 
						 | 
					@ -720,9 +728,80 @@ audio-mic-volume-low</property>
 | 
				
			||||||
            </child>
 | 
					            </child>
 | 
				
			||||||
          </object>
 | 
					          </object>
 | 
				
			||||||
          <packing>
 | 
					          <packing>
 | 
				
			||||||
            <property name="expand">True</property>
 | 
					            <property name="resize">False</property>
 | 
				
			||||||
            <property name="fill">True</property>
 | 
					            <property name="shrink">True</property>
 | 
				
			||||||
            <property name="position">0</property>
 | 
					          </packing>
 | 
				
			||||||
 | 
					        </child>
 | 
				
			||||||
 | 
					        <child>
 | 
				
			||||||
 | 
					          <object class="GtkBox" id="video_hbox">
 | 
				
			||||||
 | 
					            <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					            <property name="no_show_all">True</property>
 | 
				
			||||||
 | 
					            <property name="spacing">6</property>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkFixed" id="outgoing_fixed">
 | 
				
			||||||
 | 
					                <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                <property name="no_show_all">True</property>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkViewport" id="viewport1">
 | 
				
			||||||
 | 
					                    <property name="width_request">160</property>
 | 
				
			||||||
 | 
					                    <property name="height_request">120</property>
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                    <child>
 | 
				
			||||||
 | 
					                      <object class="GtkDrawingArea" id="outgoing_drawingarea">
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                        <property name="double_buffered">False</property>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">False</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">0</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					            <child>
 | 
				
			||||||
 | 
					              <object class="GtkBox" id="vbox1">
 | 
				
			||||||
 | 
					                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                <property name="orientation">vertical</property>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <object class="GtkViewport" id="viewport2">
 | 
				
			||||||
 | 
					                    <property name="visible">True</property>
 | 
				
			||||||
 | 
					                    <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                    <child>
 | 
				
			||||||
 | 
					                      <object class="GtkDrawingArea" id="incoming_drawingarea">
 | 
				
			||||||
 | 
					                        <property name="width_request">320</property>
 | 
				
			||||||
 | 
					                        <property name="height_request">240</property>
 | 
				
			||||||
 | 
					                        <property name="visible">True</property>
 | 
				
			||||||
 | 
					                        <property name="can_focus">False</property>
 | 
				
			||||||
 | 
					                        <property name="double_buffered">False</property>
 | 
				
			||||||
 | 
					                      </object>
 | 
				
			||||||
 | 
					                    </child>
 | 
				
			||||||
 | 
					                  </object>
 | 
				
			||||||
 | 
					                  <packing>
 | 
				
			||||||
 | 
					                    <property name="expand">True</property>
 | 
				
			||||||
 | 
					                    <property name="fill">True</property>
 | 
				
			||||||
 | 
					                    <property name="position">0</property>
 | 
				
			||||||
 | 
					                  </packing>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					                <child>
 | 
				
			||||||
 | 
					                  <placeholder/>
 | 
				
			||||||
 | 
					                </child>
 | 
				
			||||||
 | 
					              </object>
 | 
				
			||||||
 | 
					              <packing>
 | 
				
			||||||
 | 
					                <property name="expand">True</property>
 | 
				
			||||||
 | 
					                <property name="fill">True</property>
 | 
				
			||||||
 | 
					                <property name="position">1</property>
 | 
				
			||||||
 | 
					              </packing>
 | 
				
			||||||
 | 
					            </child>
 | 
				
			||||||
 | 
					          </object>
 | 
				
			||||||
 | 
					          <packing>
 | 
				
			||||||
 | 
					            <property name="resize">True</property>
 | 
				
			||||||
 | 
					            <property name="shrink">True</property>
 | 
				
			||||||
          </packing>
 | 
					          </packing>
 | 
				
			||||||
        </child>
 | 
					        </child>
 | 
				
			||||||
      </object>
 | 
					      </object>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2363,7 +2363,7 @@ $T will be replaced by auto-not-available timeout</property>
 | 
				
			||||||
                  </object>
 | 
					                  </object>
 | 
				
			||||||
                  <packing>
 | 
					                  <packing>
 | 
				
			||||||
                    <property name="expand">False</property>
 | 
					                    <property name="expand">False</property>
 | 
				
			||||||
                    <property name="fill">True</property>
 | 
					                    <property name="fill">False</property>
 | 
				
			||||||
                    <property name="position">0</property>
 | 
					                    <property name="position">0</property>
 | 
				
			||||||
                  </packing>
 | 
					                  </packing>
 | 
				
			||||||
                </child>
 | 
					                </child>
 | 
				
			||||||
| 
						 | 
					@ -2386,7 +2386,7 @@ $T will be replaced by auto-not-available timeout</property>
 | 
				
			||||||
                          <object class="GtkTable" id="table8">
 | 
					                          <object class="GtkTable" id="table8">
 | 
				
			||||||
                            <property name="visible">True</property>
 | 
					                            <property name="visible">True</property>
 | 
				
			||||||
                            <property name="can_focus">False</property>
 | 
					                            <property name="can_focus">False</property>
 | 
				
			||||||
                            <property name="n_rows">4</property>
 | 
					                            <property name="n_rows">5</property>
 | 
				
			||||||
                            <property name="n_columns">2</property>
 | 
					                            <property name="n_columns">2</property>
 | 
				
			||||||
                            <property name="column_spacing">6</property>
 | 
					                            <property name="column_spacing">6</property>
 | 
				
			||||||
                            <property name="row_spacing">6</property>
 | 
					                            <property name="row_spacing">6</property>
 | 
				
			||||||
| 
						 | 
					@ -2494,6 +2494,21 @@ $T will be replaced by auto-not-available timeout</property>
 | 
				
			||||||
                                <property name="y_options"/>
 | 
					                                <property name="y_options"/>
 | 
				
			||||||
                              </packing>
 | 
					                              </packing>
 | 
				
			||||||
                            </child>
 | 
					                            </child>
 | 
				
			||||||
 | 
					                            <child>
 | 
				
			||||||
 | 
					                              <object class="GtkCheckButton" id="video_see_self_checkbutton">
 | 
				
			||||||
 | 
					                                <property name="label" translatable="yes">View own video source</property>
 | 
				
			||||||
 | 
					                                <property name="visible">True</property>
 | 
				
			||||||
 | 
					                                <property name="can_focus">True</property>
 | 
				
			||||||
 | 
					                                <property name="receives_default">False</property>
 | 
				
			||||||
 | 
					                                <property name="xalign">0</property>
 | 
				
			||||||
 | 
					                                <property name="draw_indicator">True</property>
 | 
				
			||||||
 | 
					                              </object>
 | 
				
			||||||
 | 
					                              <packing>
 | 
				
			||||||
 | 
					                                <property name="right_attach">2</property>
 | 
				
			||||||
 | 
					                                <property name="top_attach">4</property>
 | 
				
			||||||
 | 
					                                <property name="bottom_attach">5</property>
 | 
				
			||||||
 | 
					                              </packing>
 | 
				
			||||||
 | 
					                            </child>
 | 
				
			||||||
                          </object>
 | 
					                          </object>
 | 
				
			||||||
                        </child>
 | 
					                        </child>
 | 
				
			||||||
                      </object>
 | 
					                      </object>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1798,7 +1798,7 @@ class ChatControl(ChatControlBase):
 | 
				
			||||||
        # Video buttons
 | 
					        # Video buttons
 | 
				
			||||||
        self._video_button.set_sensitive(self.video_available)
 | 
					        self._video_button.set_sensitive(self.video_available)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # change tooltip text for audio and video buttons if python-farstream is
 | 
					        # change tooltip text for audio and video buttons if farstream is
 | 
				
			||||||
        # not installed
 | 
					        # not installed
 | 
				
			||||||
        audio_tooltip_text = _('Toggle audio session') + '\n'
 | 
					        audio_tooltip_text = _('Toggle audio session') + '\n'
 | 
				
			||||||
        video_tooltip_text = _('Toggle video session') + '\n'
 | 
					        video_tooltip_text = _('Toggle video session') + '\n'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -184,16 +184,19 @@ except Exception:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
HAVE_FARSTREAM = True
 | 
					HAVE_FARSTREAM = True
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    raise ImportError
 | 
					    import gi
 | 
				
			||||||
    farstream = __import__('farstream')
 | 
					    gi.require_version('Farstream', '0.2')
 | 
				
			||||||
    import gst
 | 
					    from gi.repository import Farstream
 | 
				
			||||||
    import glib
 | 
					    gi.require_version('Gst', '1.0')
 | 
				
			||||||
 | 
					    from gi.repository import Gst
 | 
				
			||||||
 | 
					    from gi.repository import GLib
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        conference = gst.element_factory_make('fsrtpconference')
 | 
					        Gst.init(None)
 | 
				
			||||||
        session = conference.new_session(farstream.MEDIA_TYPE_AUDIO)
 | 
					        conference = Gst.ElementFactory.make('fsrtpconference', None)
 | 
				
			||||||
 | 
					        session = conference.new_session(Farstream.MediaType.AUDIO)
 | 
				
			||||||
        del session
 | 
					        del session
 | 
				
			||||||
        del conference
 | 
					        del conference
 | 
				
			||||||
    except:
 | 
					    except GLib.GError:
 | 
				
			||||||
        HAVE_FARSTREAM = False
 | 
					        HAVE_FARSTREAM = False
 | 
				
			||||||
 | 
					
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ class ConnectionJingle(object):
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
        if sid in self._sessions:
 | 
					        if sid in self._sessions:
 | 
				
			||||||
            #FIXME: Move this elsewhere?
 | 
					            #FIXME: Move this elsewhere?
 | 
				
			||||||
            for content in self._sessions[sid].contents.values():
 | 
					            for content in list(self._sessions[sid].contents.values()):
 | 
				
			||||||
                content.destroy()
 | 
					                content.destroy()
 | 
				
			||||||
            self._sessions[sid].callbacks = []
 | 
					            self._sessions[sid].callbacks = []
 | 
				
			||||||
            del self._sessions[sid]
 | 
					            del self._sessions[sid]
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,9 +23,11 @@ from gi.repository import GLib
 | 
				
			||||||
import socket
 | 
					import socket
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import nbxmpp
 | 
					import nbxmpp
 | 
				
			||||||
import farstream
 | 
					from gi.repository import Farstream
 | 
				
			||||||
import gst
 | 
					import gi
 | 
				
			||||||
from glib import GError
 | 
					gi.require_version('Gst', '1.0')
 | 
				
			||||||
 | 
					from gi.repository import Gst
 | 
				
			||||||
 | 
					from gi.repository import GLib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from common import gajim
 | 
					from common import gajim
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,8 +47,8 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
        JingleContent.__init__(self, session, transport)
 | 
					        JingleContent.__init__(self, session, transport)
 | 
				
			||||||
        self.media = media
 | 
					        self.media = media
 | 
				
			||||||
        self._dtmf_running = False
 | 
					        self._dtmf_running = False
 | 
				
			||||||
        self.farstream_media = {'audio': farstream.MEDIA_TYPE_AUDIO,
 | 
					        self.farstream_media = {'audio': Farstream.MediaType.AUDIO,
 | 
				
			||||||
                                                        'video': farstream.MEDIA_TYPE_VIDEO}[media]
 | 
					            'video': Farstream.MediaType.VIDEO}[media]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.pipeline = None
 | 
					        self.pipeline = None
 | 
				
			||||||
        self.src_bin = None
 | 
					        self.src_bin = None
 | 
				
			||||||
| 
						 | 
					@ -64,13 +66,13 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def setup_stream(self, on_src_pad_added):
 | 
					    def setup_stream(self, on_src_pad_added):
 | 
				
			||||||
        # pipeline and bus
 | 
					        # pipeline and bus
 | 
				
			||||||
        self.pipeline = gst.Pipeline()
 | 
					        self.pipeline = Gst.Pipeline()
 | 
				
			||||||
        bus = self.pipeline.get_bus()
 | 
					        bus = self.pipeline.get_bus()
 | 
				
			||||||
        bus.add_signal_watch()
 | 
					        bus.add_signal_watch()
 | 
				
			||||||
        bus.connect('message', self._on_gst_message)
 | 
					        bus.connect('message', self._on_gst_message)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # conference
 | 
					        # conference
 | 
				
			||||||
        self.conference = gst.element_factory_make('fsrtpconference')
 | 
					        self.conference = Gst.ElementFactory.make('fsrtpconference', None)
 | 
				
			||||||
        self.pipeline.add(self.conference)
 | 
					        self.pipeline.add(self.conference)
 | 
				
			||||||
        self.funnel = None
 | 
					        self.funnel = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -80,6 +82,7 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
        # FIXME: Consider a workaround, here...
 | 
					        # FIXME: Consider a workaround, here...
 | 
				
			||||||
        # pidgin and telepathy-gabble don't follow the XEP, and it won't work
 | 
					        # pidgin and telepathy-gabble don't follow the XEP, and it won't work
 | 
				
			||||||
        # due to bad controlling-mode
 | 
					        # due to bad controlling-mode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        params = {'controlling-mode': self.session.weinitiate, 'debug': False}
 | 
					        params = {'controlling-mode': self.session.weinitiate, 'debug': False}
 | 
				
			||||||
        if gajim.config.get('use_stun_server'):
 | 
					        if gajim.config.get('use_stun_server'):
 | 
				
			||||||
            stun_server = gajim.config.get('stun_server')
 | 
					            stun_server = gajim.config.get('stun_server')
 | 
				
			||||||
| 
						 | 
					@ -88,16 +91,16 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
            if stun_server:
 | 
					            if stun_server:
 | 
				
			||||||
                try:
 | 
					                try:
 | 
				
			||||||
                    ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
 | 
					                    ip = socket.getaddrinfo(stun_server, 0, socket.AF_UNSPEC,
 | 
				
			||||||
                            socket.SOCK_STREAM)[0][4][0]
 | 
					                        socket.SOCK_STREAM)[0][4][0]
 | 
				
			||||||
                except socket.gaierror as e:
 | 
					                except socket.gaierror as e:
 | 
				
			||||||
                    log.warning('Lookup of stun ip failed: %s' % str(e))
 | 
					                    log.warning('Lookup of stun ip failed: %s' % str(e))
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    params['stun-ip'] = ip
 | 
					                    params['stun-ip'] = ip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.p2pstream = self.p2psession.new_stream(participant,
 | 
					        self.p2pstream = self.p2psession.new_stream(participant,
 | 
				
			||||||
                farstream.DIRECTION_RECV)
 | 
					            Farstream.StreamDirection.RECV)
 | 
				
			||||||
        self.p2pstream.connect('src-pad-added', on_src_pad_added)
 | 
					        self.p2pstream.connect('src-pad-added', on_src_pad_added)
 | 
				
			||||||
        self.p2pstream.set_transmitter('nice', params)
 | 
					        self.p2pstream.set_transmitter_ht('nice', params)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def is_ready(self):
 | 
					    def is_ready(self):
 | 
				
			||||||
        return (JingleContent.is_ready(self) and self.candidates_ready)
 | 
					        return (JingleContent.is_ready(self) and self.candidates_ready)
 | 
				
			||||||
| 
						 | 
					@ -105,9 +108,9 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
    def make_bin_from_config(self, config_key, pipeline, text):
 | 
					    def make_bin_from_config(self, config_key, pipeline, text):
 | 
				
			||||||
        pipeline = pipeline % gajim.config.get(config_key)
 | 
					        pipeline = pipeline % gajim.config.get(config_key)
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
            bin = gst.parse_bin_from_description(pipeline, True)
 | 
					            bin = Gst.parse_bin_from_description(pipeline, True)
 | 
				
			||||||
            return bin
 | 
					            return bin
 | 
				
			||||||
        except GError as e:
 | 
					        except GLib.GError as e:
 | 
				
			||||||
            gajim.nec.push_incoming_event(InformationEvent(None,
 | 
					            gajim.nec.push_incoming_event(InformationEvent(None,
 | 
				
			||||||
                conn=self.session.connection, level='error',
 | 
					                conn=self.session.connection, level='error',
 | 
				
			||||||
                pri_txt=_('%s configuration error') % text.capitalize(),
 | 
					                pri_txt=_('%s configuration error') % text.capitalize(),
 | 
				
			||||||
| 
						 | 
					@ -144,8 +147,8 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _start_dtmf(self, event):
 | 
					    def _start_dtmf(self, event):
 | 
				
			||||||
        if event in ('*', '#'):
 | 
					        if event in ('*', '#'):
 | 
				
			||||||
            event = {'*': farstream.DTMF_EVENT_STAR,
 | 
					            event = {'*': Farstream.DTMFEvent.STAR,
 | 
				
			||||||
                    '#': farstream.DTMF_EVENT_POUND}[event]
 | 
					                '#': Farstream.DTMFEvent.POUND}[event]
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            event = int(event)
 | 
					            event = int(event)
 | 
				
			||||||
        self.p2psession.start_telephony_event(event, 2)
 | 
					        self.p2psession.start_telephony_event(event, 2)
 | 
				
			||||||
| 
						 | 
					@ -155,30 +158,30 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _fill_content(self, content):
 | 
					    def _fill_content(self, content):
 | 
				
			||||||
        content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
 | 
					        content.addChild(nbxmpp.NS_JINGLE_RTP + ' description',
 | 
				
			||||||
                attrs={'media': self.media}, payload=self.iter_codecs())
 | 
					            attrs={'media': self.media}, payload=self.iter_codecs())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _setup_funnel(self):
 | 
					    def _setup_funnel(self):
 | 
				
			||||||
        self.funnel = gst.element_factory_make('fsfunnel')
 | 
					        self.funnel = Gst.ElementFactory.make('funnel', None)
 | 
				
			||||||
        self.pipeline.add(self.funnel)
 | 
					        self.pipeline.add(self.funnel)
 | 
				
			||||||
        self.funnel.set_state(gst.STATE_PLAYING)
 | 
					 | 
				
			||||||
        self.sink.set_state(gst.STATE_PLAYING)
 | 
					 | 
				
			||||||
        self.funnel.link(self.sink)
 | 
					        self.funnel.link(self.sink)
 | 
				
			||||||
 | 
					        self.sink.set_state(Gst.State.PLAYING)
 | 
				
			||||||
 | 
					        self.funnel.set_state(Gst.State.PLAYING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _on_src_pad_added(self, stream, pad, codec):
 | 
					    def _on_src_pad_added(self, stream, pad, codec):
 | 
				
			||||||
        if not self.funnel:
 | 
					        if not self.funnel:
 | 
				
			||||||
            self._setup_funnel()
 | 
					            self._setup_funnel()
 | 
				
			||||||
        pad.link(self.funnel.get_pad('sink%d'))
 | 
					        pad.link(self.funnel.get_request_pad('sink_%u'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _on_gst_message(self, bus, message):
 | 
					    def _on_gst_message(self, bus, message):
 | 
				
			||||||
        if message.type == gst.MESSAGE_ELEMENT:
 | 
					        if message.type == Gst.MessageType.ELEMENT:
 | 
				
			||||||
            name = message.structure.get_name()
 | 
					            name = message.get_structure().get_name()
 | 
				
			||||||
            log.debug('gst element message: %s: %s' % (name, message))
 | 
					            log.debug('gst element message: %s: %s' % (name, message))
 | 
				
			||||||
            if name == 'farstream-new-active-candidate-pair':
 | 
					            if name == 'farstream-new-active-candidate-pair':
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            elif name == 'farstream-recv-codecs-changed':
 | 
					            elif name == 'farstream-recv-codecs-changed':
 | 
				
			||||||
                pass
 | 
					                pass
 | 
				
			||||||
            elif name == 'farstream-codecs-changed':
 | 
					            elif name == 'farstream-codecs-changed':
 | 
				
			||||||
                if self.sent and self.p2psession.get_property('codecs'):
 | 
					                if self.sent and self.p2psession.props.codecs_without_config:
 | 
				
			||||||
                    self.send_description_info()
 | 
					                    self.send_description_info()
 | 
				
			||||||
                    if self.transport.remote_candidates:
 | 
					                    if self.transport.remote_candidates:
 | 
				
			||||||
                        # those lines MUST be done after we get info on our
 | 
					                        # those lines MUST be done after we get info on our
 | 
				
			||||||
| 
						 | 
					@ -187,29 +190,30 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
                            self.transport.remote_candidates)
 | 
					                            self.transport.remote_candidates)
 | 
				
			||||||
                        self.transport.remote_candidates = []
 | 
					                        self.transport.remote_candidates = []
 | 
				
			||||||
                        self.p2pstream.set_property('direction',
 | 
					                        self.p2pstream.set_property('direction',
 | 
				
			||||||
                            farstream.DIRECTION_BOTH)
 | 
					                            Farstream.StreamDirection.BOTH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            elif name == 'farstream-local-candidates-prepared':
 | 
					            elif name == 'farstream-local-candidates-prepared':
 | 
				
			||||||
                self.candidates_ready = True
 | 
					                self.candidates_ready = True
 | 
				
			||||||
                if self.is_ready():
 | 
					                if self.is_ready():
 | 
				
			||||||
                    self.session.on_session_state_changed(self)
 | 
					                    self.session.on_session_state_changed(self)
 | 
				
			||||||
            elif name == 'farstream-new-local-candidate':
 | 
					            elif name == 'farstream-new-local-candidate':
 | 
				
			||||||
                candidate = message.structure['candidate']
 | 
					                candidate = message.get_structure().get_value('candidate').copy(
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
                self.transport.candidates.append(candidate)
 | 
					                self.transport.candidates.append(candidate)
 | 
				
			||||||
                if self.sent:
 | 
					                if self.sent:
 | 
				
			||||||
                    # FIXME: Is this case even possible?
 | 
					                    # FIXME: Is this case even possible?
 | 
				
			||||||
                    self.send_candidate(candidate)
 | 
					                    self.send_candidate(candidate)
 | 
				
			||||||
            elif name == 'farstream-component-state-changed':
 | 
					            elif name == 'farstream-component-state-changed':
 | 
				
			||||||
                state = message.structure['state']
 | 
					                state = message.get_structure().get_value('state')
 | 
				
			||||||
                if state == farstream.STREAM_STATE_FAILED:
 | 
					                if state == Farstream.StreamState.FAILED:
 | 
				
			||||||
                    reason = nbxmpp.Node('reason')
 | 
					                    reason = nbxmpp.Node('reason')
 | 
				
			||||||
                    reason.setTag('failed-transport')
 | 
					                    reason.setTag('failed-transport')
 | 
				
			||||||
                    self.session.remove_content(self.creator, self.name, reason)
 | 
					                    self.session.remove_content(self.creator, self.name, reason)
 | 
				
			||||||
            elif name == 'farstream-error':
 | 
					            elif name == 'farstream-error':
 | 
				
			||||||
                log.error('Farstream error #%d!\nMessage: %s' % (
 | 
					                log.error('Farstream error #%d!\nMessage: %s' % (
 | 
				
			||||||
                    message.structure['error-no'],
 | 
					                    message.get_structure().get_value('error-no'),
 | 
				
			||||||
                    message.structure['error-msg']))
 | 
					                    message.get_structure().get_value('error-msg')))
 | 
				
			||||||
        elif message.type == gst.MESSAGE_ERROR:
 | 
					        elif message.type == Gst.MessageType.ERROR:
 | 
				
			||||||
            # TODO: Fix it to fallback to videotestsrc anytime an error occur,
 | 
					            # TODO: Fix it to fallback to videotestsrc anytime an error occur,
 | 
				
			||||||
            # or raise an error, Jingle way
 | 
					            # or raise an error, Jingle way
 | 
				
			||||||
            # or maybe one-sided stream?
 | 
					            # or maybe one-sided stream?
 | 
				
			||||||
| 
						 | 
					@ -217,21 +221,21 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
                gajim.nec.push_incoming_event(InformationEvent(None,
 | 
					                gajim.nec.push_incoming_event(InformationEvent(None,
 | 
				
			||||||
                    conn=self.session.connection, level='error',
 | 
					                    conn=self.session.connection, level='error',
 | 
				
			||||||
                    pri_txt=_('GStreamer error'), sec_txt=_('Error: %s\nDebug: '
 | 
					                    pri_txt=_('GStreamer error'), sec_txt=_('Error: %s\nDebug: '
 | 
				
			||||||
                    '%s' % (message.structure['gerror'],
 | 
					                    '%s' % (message.get_structure().get_value('gerror'),
 | 
				
			||||||
                    message.structure['debug']))))
 | 
					                    message.get_structure().get_value('debug')))))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            sink_pad = self.p2psession.get_property('sink-pad')
 | 
					            sink_pad = self.p2psession.get_property('sink-pad')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Remove old source
 | 
					            # Remove old source
 | 
				
			||||||
            self.src_bin.get_pad('src').unlink(sink_pad)
 | 
					            self.src_bin.get_static_pad('src').unlink(sink_pad)
 | 
				
			||||||
            self.src_bin.set_state(gst.STATE_NULL)
 | 
					            self.src_bin.set_state(Gst.State.NULL)
 | 
				
			||||||
            self.pipeline.remove(self.src_bin)
 | 
					            self.pipeline.remove(self.src_bin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if not self.stream_failed_once:
 | 
					            if not self.stream_failed_once:
 | 
				
			||||||
                # Add fallback source
 | 
					                # Add fallback source
 | 
				
			||||||
                self.src_bin = self.get_fallback_src()
 | 
					                self.src_bin = self.get_fallback_src()
 | 
				
			||||||
                self.pipeline.add(self.src_bin)
 | 
					                self.pipeline.add(self.src_bin)
 | 
				
			||||||
                self.src_bin.get_pad('src').link(sink_pad)
 | 
					                self.src_bin.get_static_pad('src').link(sink_pad)
 | 
				
			||||||
                self.stream_failed_once = True
 | 
					                self.stream_failed_once = True
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                reason = nbxmpp.Node('reason')
 | 
					                reason = nbxmpp.Node('reason')
 | 
				
			||||||
| 
						 | 
					@ -239,10 +243,10 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
                self.session.remove_content(self.creator, self.name, reason)
 | 
					                self.session.remove_content(self.creator, self.name, reason)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Start playing again
 | 
					            # Start playing again
 | 
				
			||||||
            self.pipeline.set_state(gst.STATE_PLAYING)
 | 
					            self.pipeline.set_state(Gst.State.PLAYING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_fallback_src(self):
 | 
					    def get_fallback_src(self):
 | 
				
			||||||
        return gst.element_factory_make('fakesrc')
 | 
					        return Gst.ElementFactory.make('fakesrc', None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def on_negotiated(self):
 | 
					    def on_negotiated(self):
 | 
				
			||||||
        if self.accepted:
 | 
					        if self.accepted:
 | 
				
			||||||
| 
						 | 
					@ -252,9 +256,9 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
                    self.p2pstream.add_remote_candidates(
 | 
					                    self.p2pstream.add_remote_candidates(
 | 
				
			||||||
                        self.transport.remote_candidates)
 | 
					                        self.transport.remote_candidates)
 | 
				
			||||||
                    self.transport.remote_candidates = []
 | 
					                    self.transport.remote_candidates = []
 | 
				
			||||||
                # TODO: farstream.DIRECTION_BOTH only if senders='both'
 | 
					                    # TODO: Farstream.StreamDirection.BOTH only if senders='both'
 | 
				
			||||||
                self.p2pstream.set_property('direction',
 | 
					#                    self.p2pstream.set_property('direction',
 | 
				
			||||||
                    farstream.DIRECTION_BOTH)
 | 
					#                        Farstream.StreamDirection.BOTH)
 | 
				
			||||||
        JingleContent.on_negotiated(self)
 | 
					        JingleContent.on_negotiated(self)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __on_remote_codecs(self, stanza, content, error, action):
 | 
					    def __on_remote_codecs(self, stanza, content, error, action):
 | 
				
			||||||
| 
						 | 
					@ -267,39 +271,39 @@ class JingleRTPContent(JingleContent):
 | 
				
			||||||
            if not codec['id'] or not codec['name'] or not codec['clockrate']:
 | 
					            if not codec['id'] or not codec['name'] or not codec['clockrate']:
 | 
				
			||||||
                # ignore invalid payload-types
 | 
					                # ignore invalid payload-types
 | 
				
			||||||
                continue
 | 
					                continue
 | 
				
			||||||
            c = farstream.Codec(int(codec['id']), codec['name'],
 | 
					            c = Farstream.Codec.new(int(codec['id']), codec['name'],
 | 
				
			||||||
                    self.farstream_media, int(codec['clockrate']))
 | 
					                    self.farstream_media, int(codec['clockrate']))
 | 
				
			||||||
            if 'channels' in codec:
 | 
					            if 'channels' in codec:
 | 
				
			||||||
                c.channels = int(codec['channels'])
 | 
					                c.channels = int(codec['channels'])
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                c.channels = 1
 | 
					                c.channels = 1
 | 
				
			||||||
            c.optional_params = [(str(p['name']), str(p['value'])) for p in \
 | 
					            for p in codec.iterTags('parameter'):
 | 
				
			||||||
                    codec.iterTags('parameter')]
 | 
					                c.add_optional_parameter(p['name'], str(p['value']))
 | 
				
			||||||
            codecs.append(c)
 | 
					            codecs.append(c)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if codecs:
 | 
					        if codecs:
 | 
				
			||||||
            # FIXME: Handle this case:
 | 
					            # FIXME: Handle this case:
 | 
				
			||||||
            # glib.GError: There was no intersection between the remote codecs and
 | 
					            # GLib.GError: There was no intersection between the remote codecs and
 | 
				
			||||||
            # the local ones
 | 
					            # the local ones
 | 
				
			||||||
            self.p2pstream.set_remote_codecs(codecs)
 | 
					            self.p2pstream.set_remote_codecs(codecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def iter_codecs(self):
 | 
					    def iter_codecs(self):
 | 
				
			||||||
        codecs = self.p2psession.get_property('codecs')
 | 
					        codecs = self.p2psession.props.codecs_without_config
 | 
				
			||||||
        for codec in codecs:
 | 
					        for codec in codecs:
 | 
				
			||||||
            attrs = {'name': codec.encoding_name,
 | 
					            attrs = {'name': codec.encoding_name,
 | 
				
			||||||
                    'id': codec.id,
 | 
					                'id': codec.id,
 | 
				
			||||||
                    'channels': codec.channels}
 | 
					                'channels': codec.channels}
 | 
				
			||||||
            if codec.clock_rate:
 | 
					            if codec.clock_rate:
 | 
				
			||||||
                attrs['clockrate'] = codec.clock_rate
 | 
					                attrs['clockrate'] = codec.clock_rate
 | 
				
			||||||
            if codec.optional_params:
 | 
					            if codec.optional_params:
 | 
				
			||||||
                payload = (nbxmpp.Node('parameter', {'name': name,
 | 
					                payload = (nbxmpp.Node('parameter', {'name': p.name,
 | 
				
			||||||
                    'value': value}) for name, value in codec.optional_params)
 | 
					                    'value': p.value}) for p in codec.optional_params)
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                payload = ()
 | 
					                payload = ()
 | 
				
			||||||
            yield nbxmpp.Node('payload-type', attrs, payload)
 | 
					            yield nbxmpp.Node('payload-type', attrs, payload)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __stop(self, *things):
 | 
					    def __stop(self, *things):
 | 
				
			||||||
        self.pipeline.set_state(gst.STATE_NULL)
 | 
					        self.pipeline.set_state(Gst.State.NULL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __del__(self):
 | 
					    def __del__(self):
 | 
				
			||||||
        self.__stop()
 | 
					        self.__stop()
 | 
				
			||||||
| 
						 | 
					@ -341,31 +345,32 @@ class JingleAudio(JingleRTPContent):
 | 
				
			||||||
        #  place 16kHz before 8kHz, as buggy psi versions will take in
 | 
					        #  place 16kHz before 8kHz, as buggy psi versions will take in
 | 
				
			||||||
        #  account only the first codec
 | 
					        #  account only the first codec
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        codecs = [farstream.Codec(farstream.CODEC_ID_ANY, 'SPEEX',
 | 
					        codecs = [Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
 | 
				
			||||||
                farstream.MEDIA_TYPE_AUDIO, 16000),
 | 
					            Farstream.MediaType.AUDIO, 16000),
 | 
				
			||||||
                farstream.Codec(farstream.CODEC_ID_ANY, 'SPEEX',
 | 
					            Farstream.Codec.new(Farstream.CODEC_ID_ANY, 'SPEEX',
 | 
				
			||||||
                farstream.MEDIA_TYPE_AUDIO, 8000)]
 | 
					            Farstream.MediaType.AUDIO, 8000)]
 | 
				
			||||||
        self.p2psession.set_codec_preferences(codecs)
 | 
					        self.p2psession.set_codec_preferences(codecs)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # the local parts
 | 
					        # the local parts
 | 
				
			||||||
        # TODO: Add queues?
 | 
					        # TODO: Add queues?
 | 
				
			||||||
        self.src_bin = self.make_bin_from_config('audio_input_device',
 | 
					        self.src_bin = self.make_bin_from_config('audio_input_device',
 | 
				
			||||||
                '%s ! audioconvert', _("audio input"))
 | 
					            '%s ! audioconvert', _("audio input"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.sink = self.make_bin_from_config('audio_output_device',
 | 
					        self.sink = self.make_bin_from_config('audio_output_device',
 | 
				
			||||||
                'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
 | 
					            'audioconvert ! volume name=gajim_out_vol ! %s', _("audio output"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.mic_volume = self.src_bin.get_by_name('gajim_vol')
 | 
					        self.mic_volume = self.src_bin.get_by_name('gajim_vol')
 | 
				
			||||||
        self.out_volume = self.sink.get_by_name('gajim_out_vol')
 | 
					        self.out_volume = self.sink.get_by_name('gajim_out_vol')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # link gst elements
 | 
					        # link gst elements
 | 
				
			||||||
        self.pipeline.add(self.sink, self.src_bin)
 | 
					        self.pipeline.add(self.sink)
 | 
				
			||||||
 | 
					        self.pipeline.add(self.src_bin)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.src_bin.get_pad('src').link(self.p2psession.get_property(
 | 
					        self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
 | 
				
			||||||
                'sink-pad'))
 | 
					            'sink-pad'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The following is needed for farstream to process ICE requests:
 | 
					        # The following is needed for farstream to process ICE requests:
 | 
				
			||||||
        self.pipeline.set_state(gst.STATE_PLAYING)
 | 
					        self.pipeline.set_state(Gst.State.PLAYING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JingleVideo(JingleRTPContent):
 | 
					class JingleVideo(JingleRTPContent):
 | 
				
			||||||
| 
						 | 
					@ -381,7 +386,7 @@ class JingleVideo(JingleRTPContent):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # the local parts
 | 
					        # the local parts
 | 
				
			||||||
        if gajim.config.get('video_framerate'):
 | 
					        if gajim.config.get('video_framerate'):
 | 
				
			||||||
            framerate = 'videorate ! video/x-raw-yuv,framerate=%s ! ' % \
 | 
					            framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \
 | 
				
			||||||
                gajim.config.get('video_framerate')
 | 
					                gajim.config.get('video_framerate')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            framerate = ''
 | 
					            framerate = ''
 | 
				
			||||||
| 
						 | 
					@ -390,11 +395,11 @@ class JingleVideo(JingleRTPContent):
 | 
				
			||||||
        except:
 | 
					        except:
 | 
				
			||||||
            w = h = None
 | 
					            w = h = None
 | 
				
			||||||
        if w and h:
 | 
					        if w and h:
 | 
				
			||||||
            video_size = 'video/x-raw-yuv,width=%s,height=%s ! ' % (w, h)
 | 
					            video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h)
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            video_size = ''
 | 
					            video_size = ''
 | 
				
			||||||
        self.src_bin = self.make_bin_from_config('video_input_device',
 | 
					        self.src_bin = self.make_bin_from_config('video_input_device',
 | 
				
			||||||
            '%%s ! %svideoscale ! %sffmpegcolorspace' % (framerate, video_size),
 | 
					            '%%s ! %svideoscale ! %svideoconvert' % (framerate, video_size),
 | 
				
			||||||
            _("video input"))
 | 
					            _("video input"))
 | 
				
			||||||
        #caps = gst.element_factory_make('capsfilter')
 | 
					        #caps = gst.element_factory_make('capsfilter')
 | 
				
			||||||
        #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
 | 
					        #caps.set_property('caps', gst.caps_from_string('video/x-raw-yuv, width=320, height=240'))
 | 
				
			||||||
| 
						 | 
					@ -403,20 +408,20 @@ class JingleVideo(JingleRTPContent):
 | 
				
			||||||
        #src_bin.link(caps)
 | 
					        #src_bin.link(caps)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.sink = self.make_bin_from_config('video_output_device',
 | 
					        self.sink = self.make_bin_from_config('video_output_device',
 | 
				
			||||||
            'videoscale ! ffmpegcolorspace ! %s force-aspect-ratio=True',
 | 
					            'videoscale ! videoconvert ! %s force-aspect-ratio=True',
 | 
				
			||||||
            _("video output"))
 | 
					            _("video output"))
 | 
				
			||||||
        self.pipeline.add(self.sink)
 | 
					        self.pipeline.add(self.sink)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.src_bin.get_pad('src').link(self.p2psession.get_property(
 | 
					        self.src_bin.get_static_pad('src').link(self.p2psession.get_property(
 | 
				
			||||||
            'sink-pad'))
 | 
					            'sink-pad'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The following is needed for farstream to process ICE requests:
 | 
					        # The following is needed for farstream to process ICE requests:
 | 
				
			||||||
        self.pipeline.set_state(gst.STATE_PLAYING)
 | 
					        self.pipeline.set_state(Gst.State.PLAYING)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def get_fallback_src(self):
 | 
					    def get_fallback_src(self):
 | 
				
			||||||
        # TODO: Use avatar?
 | 
					        # TODO: Use avatar?
 | 
				
			||||||
        pipeline = 'videotestsrc is-live=true ! video/x-raw-yuv,framerate=10/1 ! ffmpegcolorspace'
 | 
					        pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert'
 | 
				
			||||||
        return gst.parse_bin_from_description(pipeline, True)
 | 
					        return Gst.parse_bin_from_description(pipeline, True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_content(desc):
 | 
					def get_content(desc):
 | 
				
			||||||
    if desc['media'] == 'audio':
 | 
					    if desc['media'] == 'audio':
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -343,8 +343,7 @@ class JingleTransportIBB(JingleTransport):
 | 
				
			||||||
        return transport
 | 
					        return transport
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    raise ImportError
 | 
					    from gi.repository import Farstream
 | 
				
			||||||
    import farstream
 | 
					 | 
				
			||||||
except Exception:
 | 
					except Exception:
 | 
				
			||||||
    pass
 | 
					    pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -353,24 +352,24 @@ class JingleTransportICEUDP(JingleTransport):
 | 
				
			||||||
        JingleTransport.__init__(self, TransportType.ICEUDP)
 | 
					        JingleTransport.__init__(self, TransportType.ICEUDP)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def make_candidate(self, candidate):
 | 
					    def make_candidate(self, candidate):
 | 
				
			||||||
        types = {farstream.CANDIDATE_TYPE_HOST: 'host',
 | 
					        types = {Farstream.CandidateType.HOST: 'host',
 | 
				
			||||||
                farstream.CANDIDATE_TYPE_SRFLX: 'srflx',
 | 
					            Farstream.CandidateType.SRFLX: 'srflx',
 | 
				
			||||||
                farstream.CANDIDATE_TYPE_PRFLX: 'prflx',
 | 
					            Farstream.CandidateType.PRFLX: 'prflx',
 | 
				
			||||||
                farstream.CANDIDATE_TYPE_RELAY: 'relay',
 | 
					            Farstream.CandidateType.RELAY: 'relay',
 | 
				
			||||||
                farstream.CANDIDATE_TYPE_MULTICAST: 'multicast'}
 | 
					            Farstream.CandidateType.MULTICAST: 'multicast'}
 | 
				
			||||||
        attrs = {
 | 
					        attrs = {
 | 
				
			||||||
                'component': candidate.component_id,
 | 
					            'component': candidate.component_id,
 | 
				
			||||||
                'foundation': '1', # hack
 | 
					            'foundation': '1', # hack
 | 
				
			||||||
                'generation': '0',
 | 
					            'generation': '0',
 | 
				
			||||||
                'ip': candidate.ip,
 | 
					            'ip': candidate.ip,
 | 
				
			||||||
                'network': '0',
 | 
					            'network': '0',
 | 
				
			||||||
                'port': candidate.port,
 | 
					            'port': candidate.port,
 | 
				
			||||||
                'priority': int(candidate.priority), # hack
 | 
					            'priority': int(candidate.priority), # hack
 | 
				
			||||||
                'id': gajim.get_an_id()
 | 
					            'id': gajim.get_an_id()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if candidate.type in types:
 | 
					        if candidate.type in types:
 | 
				
			||||||
            attrs['type'] = types[candidate.type]
 | 
					            attrs['type'] = types[candidate.type]
 | 
				
			||||||
        if candidate.proto == farstream.NETWORK_PROTOCOL_UDP:
 | 
					        if candidate.proto == Farstream.NetworkProtocol.UDP:
 | 
				
			||||||
            attrs['protocol'] = 'udp'
 | 
					            attrs['protocol'] = 'udp'
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            # we actually don't handle properly different tcp options in jingle
 | 
					            # we actually don't handle properly different tcp options in jingle
 | 
				
			||||||
| 
						 | 
					@ -389,33 +388,37 @@ class JingleTransportICEUDP(JingleTransport):
 | 
				
			||||||
    def parse_transport_stanza(self, transport):
 | 
					    def parse_transport_stanza(self, transport):
 | 
				
			||||||
        candidates = []
 | 
					        candidates = []
 | 
				
			||||||
        for candidate in transport.iterTags('candidate'):
 | 
					        for candidate in transport.iterTags('candidate'):
 | 
				
			||||||
            cand = farstream.Candidate()
 | 
					            foundation = str(candidate['foundation'])
 | 
				
			||||||
            cand.component_id = int(candidate['component'])
 | 
					            component_id = int(candidate['component'])
 | 
				
			||||||
            cand.ip = str(candidate['ip'])
 | 
					            ip = str(candidate['ip'])
 | 
				
			||||||
            cand.port = int(candidate['port'])
 | 
					            port = int(candidate['port'])
 | 
				
			||||||
            cand.foundation = str(candidate['foundation'])
 | 
					            base_ip = None
 | 
				
			||||||
            #cand.type = farstream.CANDIDATE_TYPE_LOCAL
 | 
					            base_port = 0
 | 
				
			||||||
            cand.priority = int(candidate['priority'])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if candidate['protocol'] == 'udp':
 | 
					            if candidate['protocol'] == 'udp':
 | 
				
			||||||
                cand.proto = farstream.NETWORK_PROTOCOL_UDP
 | 
					                proto = Farstream.NetworkProtocol.UDP
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # we actually don't handle properly different tcp options in jingle
 | 
					                # we actually don't handle properly different tcp options in
 | 
				
			||||||
                cand.proto = farstream.NETWORK_PROTOCOL_TCP
 | 
					                # jingle
 | 
				
			||||||
 | 
					                proto = Farstream.NetworkProtocol.TCP
 | 
				
			||||||
            cand.username = str(transport['ufrag'])
 | 
					            priority = int(candidate['priority'])
 | 
				
			||||||
            cand.password = str(transport['pwd'])
 | 
					            types = {'host': Farstream.CandidateType.HOST,
 | 
				
			||||||
 | 
					                'srflx': Farstream.CandidateType.SRFLX,
 | 
				
			||||||
            #FIXME: huh?
 | 
					                'prflx': Farstream.CandidateType.PRFLX,
 | 
				
			||||||
            types = {'host': farstream.CANDIDATE_TYPE_HOST,
 | 
					                'relay': Farstream.CandidateType.RELAY,
 | 
				
			||||||
                                    'srflx': farstream.CANDIDATE_TYPE_SRFLX,
 | 
					                'multicast': Farstream.CandidateType.MULTICAST}
 | 
				
			||||||
                                    'prflx': farstream.CANDIDATE_TYPE_PRFLX,
 | 
					 | 
				
			||||||
                                    'relay': farstream.CANDIDATE_TYPE_RELAY,
 | 
					 | 
				
			||||||
                                    'multicast': farstream.CANDIDATE_TYPE_MULTICAST}
 | 
					 | 
				
			||||||
            if 'type' in candidate and candidate['type'] in types:
 | 
					            if 'type' in candidate and candidate['type'] in types:
 | 
				
			||||||
                cand.type = types[candidate['type']]
 | 
					                type_ = types[candidate['type']]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                print('Unknown type %s' % candidate['type'])
 | 
					                log.warning('Unknown type %s' % candidate['type'])
 | 
				
			||||||
 | 
					                type_ = Farstream.CandidateType.HOST
 | 
				
			||||||
 | 
					            username = str(transport['ufrag'])
 | 
				
			||||||
 | 
					            password = str(transport['pwd'])
 | 
				
			||||||
 | 
					            ttl = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            cand = Farstream.Candidate.new_full(foundation, component_id, ip,
 | 
				
			||||||
 | 
					                port, base_ip, base_port, proto, priority, type_, username,
 | 
				
			||||||
 | 
					                password, ttl)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            candidates.append(cand)
 | 
					            candidates.append(cand)
 | 
				
			||||||
        self.remote_candidates.extend(candidates)
 | 
					        self.remote_candidates.extend(candidates)
 | 
				
			||||||
        return candidates
 | 
					        return candidates
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@
 | 
				
			||||||
## You should have received a copy of the GNU General Public License
 | 
					## You should have received a copy of the GNU General Public License
 | 
				
			||||||
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
					## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import gst
 | 
					from gi.repository import Gst
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DeviceManager(object):
 | 
					class DeviceManager(object):
 | 
				
			||||||
| 
						 | 
					@ -29,29 +29,24 @@ class DeviceManager(object):
 | 
				
			||||||
        return self.devices
 | 
					        return self.devices
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def detect_element(self, name, text, pipe='%s'):
 | 
					    def detect_element(self, name, text, pipe='%s'):
 | 
				
			||||||
        try:
 | 
					        if Gst.ElementFactory.find(name):
 | 
				
			||||||
            if gst.element_factory_find(name):
 | 
					            element = Gst.ElementFactory.make(name, '%spresencetest' % name)
 | 
				
			||||||
                element = gst.element_factory_make(name,
 | 
					            if hasattr(element.props, 'device'):
 | 
				
			||||||
                    '%spresencetest' % name)
 | 
					                element.set_state(Gst.State.READY)
 | 
				
			||||||
                if isinstance(element, gst.interfaces.PropertyProbe):
 | 
					                devices = element.get_properties('device')
 | 
				
			||||||
                    element.set_state(gst.STATE_READY)
 | 
					                if devices:
 | 
				
			||||||
                    element.probe_property_name('device')
 | 
					                    self.devices[text % _(' Default device')] = pipe % name
 | 
				
			||||||
                    devices = element.probe_get_values_name('device')
 | 
					                    for device in devices:
 | 
				
			||||||
                    if devices:
 | 
					                        element.set_state(Gst.State.NULL)
 | 
				
			||||||
                        self.devices[text % _(' Default device')] = pipe % name
 | 
					                        element.set_property('device', device)
 | 
				
			||||||
                        for device in devices:
 | 
					                        element.set_state(Gst.State.READY)
 | 
				
			||||||
                            element.set_state(gst.STATE_NULL)
 | 
					                        device_name = element.get_property('device-name')
 | 
				
			||||||
                            element.set_property('device', device)
 | 
					                        self.devices[text % device_name] = pipe % \
 | 
				
			||||||
                            element.set_state(gst.STATE_READY)
 | 
					                            '%s device=%s' % (name, device)
 | 
				
			||||||
                            device_name = element.get_property('device-name')
 | 
					                element.set_state(Gst.State.NULL)
 | 
				
			||||||
                            self.devices[text % device_name] = pipe % \
 | 
					            else:
 | 
				
			||||||
                                '%s device=%s' % (name, device)
 | 
					                self.devices[text] = pipe % name
 | 
				
			||||||
                    element.set_state(gst.STATE_NULL)
 | 
					        else:
 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    self.devices[text] = pipe % name
 | 
					 | 
				
			||||||
        except ImportError:
 | 
					 | 
				
			||||||
            pass
 | 
					 | 
				
			||||||
        except gst.ElementNotFoundError:
 | 
					 | 
				
			||||||
            print('element \'%s\' not found' % name)
 | 
					            print('element \'%s\' not found' % name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -65,7 +65,6 @@ from common import gpg
 | 
				
			||||||
from common import ged
 | 
					from common import ged
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    raise ImportError
 | 
					 | 
				
			||||||
    from common.multimedia_helpers import AudioInputManager, AudioOutputManager
 | 
					    from common.multimedia_helpers import AudioInputManager, AudioOutputManager
 | 
				
			||||||
    from common.multimedia_helpers import VideoInputManager, VideoOutputManager
 | 
					    from common.multimedia_helpers import VideoInputManager, VideoOutputManager
 | 
				
			||||||
    HAS_GST = True
 | 
					    HAS_GST = True
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,7 @@ class FeaturesWindow:
 | 
				
			||||||
                _('Requires python-docutils.')),
 | 
					                _('Requires python-docutils.')),
 | 
				
			||||||
            _('Audio / Video'): (self.farstream_available,
 | 
					            _('Audio / Video'): (self.farstream_available,
 | 
				
			||||||
                _('Ability to start audio and video chat.'),
 | 
					                _('Ability to start audio and video chat.'),
 | 
				
			||||||
                _('Requires python-farstream and gstreamer-plugins-bad.'),
 | 
					                _('Requires gir1.2-farstream-0.2, gir1.2-gstreamer-1.0, gstreamer1.0-libav and gstreamer1.0-plugins-ugly.'),
 | 
				
			||||||
                _('Feature not available under Windows.')),
 | 
					                _('Feature not available under Windows.')),
 | 
				
			||||||
            _('UPnP-IGD'): (self.gupnp_igd_available,
 | 
					            _('UPnP-IGD'): (self.gupnp_igd_available,
 | 
				
			||||||
                _('Ability to request your router to forward port for file transfer.'),
 | 
					                _('Ability to request your router to forward port for file transfer.'),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -129,7 +129,7 @@ class Interface:
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                return
 | 
					                return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            cls(obj.pri_txt, obj.sec_txt)
 | 
					            cls(obj.pri_txt, GLib.markup_escape_text(obj.sec_txt))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def handle_ask_new_nick(self, account, room_jid):
 | 
					    def handle_ask_new_nick(self, account, room_jid):
 | 
				
			||||||
        title = _('Unable to join group chat')
 | 
					        title = _('Unable to join group chat')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue