Daniel Brötzmann pushed to branch mainwindow at gajim / gajim
Commits: 2a0fc69b by wurstsalat at 2021-08-28T13:59:25+02:00 Refactor Jingle AV into separate widget
- - - - -
11 changed files:
- gajim/common/events.py - gajim/common/jingle_session.py - + gajim/data/gui/call_widget.ui - gajim/data/gui/chat_control.ui - + gajim/gtk/call_widget.py - gajim/gtk/controls/base.py - gajim/gtk/controls/chat.py - gajim/gtk/conversation/rows/call.py - gajim/gtk/conversation/view.py - gajim/gtk/main.py - gajim/gui_interface.py
Changes:
===================================== gajim/common/events.py ===================================== @@ -126,24 +126,6 @@ class FileHashErrorEvent(FileRequestEvent): type_ = 'file-hash-error'
-class JingleIncomingEvent(Event): - - type_ = 'jingle-incoming' - - def __init__(self, - peerjid, - sid, - content_types, - time_=None, - show_in_systray=True): - Event.__init__(self, - time_, - show_in_systray=show_in_systray) - self.peerjid = peerjid - self.sid = sid - self.content_types = content_types - - class Events: def __init__(self): self._events = {} # list of events {acct: {jid1: [E1, E2]}, }
===================================== gajim/common/jingle_session.py ===================================== @@ -837,10 +837,12 @@ def __transport_accept(self, transport): self.state = JingleStates.ACTIVE
def _raise_event(self, name, **kwargs): - jid, resource = app.get_room_and_nick_from_fjid(self.peerjid) + jid, resource = app.get_room_and_nick_from_fjid( + str(self.peerjid)) app.nec.push_incoming_event( NetworkEvent(name, conn=self.connection, + account=self.connection.name, fjid=self.peerjid, jid=jid, sid=self.sid,
===================================== gajim/data/gui/call_widget.ui ===================================== @@ -0,0 +1,633 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkAdjustment" id="adjustment1"> + <property name="upper">110</property> + <property name="step-increment">1</property> + <property name="page-increment">10</property> + <property name="page-size">10</property> + </object> + <object class="GtkAdjustment" id="adjustment2"> + <property name="upper">110</property> + <property name="step-increment">1</property> + <property name="page-increment">10</property> + <property name="page-size">10</property> + </object> + <object class="GtkPopover" id="dtmf_popover"> + <property name="can-focus">False</property> + <child> + <!-- n-columns=3 n-rows=4 --> + <object class="GtkGrid" id="grid1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="border-width">6</property> + <child> + <object class="GtkButton" id="1_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="halign">center</property> + <property name="label" translatable="yes"><b>1</b></property> + <property name="use-markup">True</property> + <property name="xalign">0</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="2_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>2</b> abc</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="3_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>3</b> def</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="4_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>4</b> ghi</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="5_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label5"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>5</b> jkl</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="6_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label6"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>6</b> mno</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="7_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label7"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>7</b> pqrs</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="8_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label8"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>8</b> tuv</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="9_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label9"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>9</b> wxyz</property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkButton" id="*_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label10"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>*</b></property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkButton" id="0_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label11"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>0</b></property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">1</property> + <property name="top-attach">3</property> + </packing> + </child> + <child> + <object class="GtkButton" id="#_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <child> + <object class="GtkLabel" id="label12"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes"><b>#</b></property> + <property name="use-markup">True</property> + </object> + </child> + </object> + <packing> + <property name="left-attach">2</property> + <property name="top-attach">3</property> + </packing> + </child> + </object> + </child> + </object> + <object class="GtkBox" id="av_box"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="video_box"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="margin-start">6</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkViewport" id="outgoing_viewport"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="shadow-type">none</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkViewport" id="incoming_viewport"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="shadow-type">none</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox" id="av_start_box"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="halign">center</property> + <property name="orientation">vertical</property> + <property name="spacing">18</property> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes">Start Call…</property> + <style> + <class name="bold"/> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="av_start_mic_button"> + <property name="label" translatable="yes">With Microphone</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="clicked" handler="_on_call_with_mic" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="av_start_mic_cam_button"> + <property name="label" translatable="yes">Microphone & Camera</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <signal name="clicked" handler="_on_call_with_mic_and_cam" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkImage" id="jingle_audio_state"> + <property name="can-focus">False</property> + <property name="no-show-all">True</property> + <property name="pixel-size">100</property> + <property name="icon-name">audio-input-microphone-symbolic</property> + <style> + <class name="dim-label"/> + <class name="audio-mic-animation"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="jingle_connection_state"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="valign">end</property> + <property name="wrap">True</property> + <property name="max-width-chars">20</property> + <style> + <class name="bold"/> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + <child> + <object class="GtkSpinner" id="jingle_connection_spinner"> + <property name="visible">True</property> + <property name="can-focus">False</property> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">3</property> + </packing> + </child> + <child> + <object class="GtkButton" id="answer_video_button"> + <property name="label" translatable="yes">Start Video</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="no-show-all">True</property> + <property name="halign">center</property> + <signal name="clicked" handler="_on_answer_video_clicked" swapped="no"/> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">6</property> + <child> + <object class="GtkButton"> + <property name="width-request">70</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">End call</property> + <property name="halign">center</property> + <signal name="clicked" handler="_on_end_call_clicked" swapped="no"/> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">call-stop</property> + </object> + </child> + </object> + <packing> + <property name="expand">True</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkBox" id="audio_buttons_box"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">False</property> + <child> + <object class="GtkVolumeButton" id="mic_hscale"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="focus-on-click">False</property> + <property name="receives-default">True</property> + <property name="has-tooltip">True</property> + <property name="relief">none</property> + <property name="orientation">vertical</property> + <property name="size">menu</property> + <property name="adjustment">adjustment1</property> + <property name="icons">microphone-sensitivity-muted-symbolic +microphone-sensitivity-low-symbolic +microphone-sensitivity-medium-symbolic +microphone-sensitivity-high-symbolic</property> + <property name="use-symbolic">False</property> + <signal name="value-changed" handler="_on_mic_volume_changed" swapped="no"/> + <child internal-child="plus_button"> + <object class="GtkButton" id="volumebutton-plus_button2"> + <property name="can-focus">False</property> + <property name="receives-default">False</property> + </object> + </child> + <child internal-child="minus_button"> + <object class="GtkButton" id="volumebutton-minus_button2"> + <property name="can-focus">False</property> + <property name="receives-default">False</property> + </object> + </child> + <style> + <class name="chatcontrol-actionbar-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkVolumeButton" id="sound_hscale"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="focus-on-click">False</property> + <property name="receives-default">True</property> + <property name="has-tooltip">True</property> + <property name="relief">none</property> + <property name="orientation">vertical</property> + <property name="size">menu</property> + <property name="adjustment">adjustment2</property> + <property name="icons">audio-volume-muted-symbolic +audio-volume-low-symbolic +audio-volume-medium-symbolic +audio-volume-high-symbolic</property> + <property name="use-symbolic">False</property> + <signal name="value-changed" handler="_on_output_volume_changed" swapped="no"/> + <child internal-child="plus_button"> + <object class="GtkButton" id="volumebutton-plus_button1"> + <property name="can-focus">False</property> + <property name="receives-default">False</property> + </object> + </child> + <child internal-child="minus_button"> + <object class="GtkButton" id="volumebutton-minus_button1"> + <property name="can-focus">False</property> + <property name="receives-default">False</property> + </object> + </child> + <style> + <class name="chatcontrol-actionbar-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="dtmf_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="relief">none</property> + <property name="direction">up</property> + <property name="popover">dtmf_popover</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">input-dialpad-symbolic</property> + <property name="icon_size">1</property> + </object> + </child> + <style> + <class name="chatcontrol-actionbar-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">4</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="av_cam_button"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Turn Camera on</property> + <property name="relief">none</property> + <signal name="clicked" handler="_on_video" swapped="no"/> + <child> + <object class="GtkImage" id="av_cam_image"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">feather-camera-symbolic</property> + </object> + </child> + <style> + <class name="chatcontrol-actionbar-button"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> +</interface>
===================================== gajim/data/gui/chat_control.ui ===================================== @@ -1,19 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.38.2 --> <interface> - <requires lib="gtk+" version="3.22"/> - <object class="GtkAdjustment" id="adjustment1"> - <property name="upper">110</property> - <property name="step-increment">1</property> - <property name="page-increment">10</property> - <property name="page-size">10</property> - </object> - <object class="GtkAdjustment" id="adjustment2"> - <property name="upper">110</property> - <property name="step-increment">1</property> - <property name="page-increment">10</property> - <property name="page-size">10</property> - </object> + <requires lib="gtk+" version="3.24"/> <object class="GtkBox" id="drop_area"> <property name="name">DropArea</property> <property name="visible">True</property> @@ -59,259 +47,6 @@ </packing> </child> </object> - <object class="GtkPopover" id="dtmf_popover"> - <property name="can-focus">False</property> - <child> - <!-- n-columns=3 n-rows=4 --> - <object class="GtkGrid" id="grid1"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="border-width">6</property> - <child> - <object class="GtkButton" id="1_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label1"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="halign">center</property> - <property name="label" translatable="yes"><b>1</b></property> - <property name="use-markup">True</property> - <property name="xalign">0</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="2_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>2</b> abc</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="3_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label3"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>3</b> def</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="4_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label4"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>4</b> ghi</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="5_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label5"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>5</b> jkl</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="6_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label6"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>6</b> mno</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="7_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label7"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>7</b> pqrs</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="8_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label8"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>8</b> tuv</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="9_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label9"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>9</b> wxyz</property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkButton" id="*_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label10"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>*</b></property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkButton" id="0_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label11"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>0</b></property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">3</property> - </packing> - </child> - <child> - <object class="GtkButton" id="#_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <child> - <object class="GtkLabel" id="label12"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes"><b>#</b></property> - <property name="use-markup">True</property> - </object> - </child> - </object> - <packing> - <property name="left-attach">2</property> - <property name="top-attach">3</property> - </packing> - </child> - </object> - </child> - </object> <object class="GtkMenu" id="formattings_menu"> <property name="visible">True</property> <property name="can-focus">False</property> @@ -869,354 +604,7 @@ </packing> </child> <child> - <object class="GtkBox" id="av_box"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkBox" id="video_box"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="margin-start">6</property> - <property name="orientation">vertical</property> - <child> - <object class="GtkViewport" id="outgoing_viewport"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="shadow-type">none</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkViewport" id="incoming_viewport"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="shadow-type">none</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">center</property> - <property name="orientation">vertical</property> - <property name="spacing">6</property> - <child> - <object class="GtkBox" id="av_start_box"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="halign">center</property> - <property name="orientation">vertical</property> - <property name="spacing">18</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Start Call…</property> - <style> - <class name="bold"/> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="av_start_mic_button"> - <property name="label" translatable="yes">With Microphone</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <signal name="clicked" handler="_on_call_with_mic" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="av_start_mic_cam_button"> - <property name="label" translatable="yes">Microphone & Camera</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <signal name="clicked" handler="_on_call_with_mic_and_cam" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkImage" id="jingle_audio_state"> - <property name="can-focus">False</property> - <property name="no-show-all">True</property> - <property name="pixel-size">100</property> - <property name="icon-name">audio-input-microphone-symbolic</property> - <style> - <class name="dim-label"/> - <class name="audio-mic-animation"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="jingle_connection_state"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="valign">end</property> - <property name="wrap">True</property> - <property name="max-width-chars">20</property> - <style> - <class name="bold"/> - <class name="dim-label"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - <child> - <object class="GtkSpinner" id="jingle_connection_spinner"> - <property name="visible">True</property> - <property name="can-focus">False</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">3</property> - </packing> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">6</property> - <child> - <object class="GtkButton"> - <property name="width-request">70</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes">End call</property> - <property name="halign">center</property> - <signal name="clicked" handler="_on_end_call_clicked" swapped="no"/> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">call-stop</property> - </object> - </child> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="audio_buttons_box"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can-focus">False</property> - <child> - <object class="GtkVolumeButton" id="mic_hscale"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="focus-on-click">False</property> - <property name="receives-default">True</property> - <property name="has-tooltip">True</property> - <property name="relief">none</property> - <property name="orientation">vertical</property> - <property name="size">menu</property> - <property name="adjustment">adjustment2</property> - <property name="icons">microphone-sensitivity-muted-symbolic -microphone-sensitivity-low-symbolic -microphone-sensitivity-medium-symbolic -microphone-sensitivity-high-symbolic</property> - <property name="use-symbolic">False</property> - <child internal-child="plus_button"> - <object class="GtkButton" id="volumebutton-plus_button2"> - <property name="can-focus">False</property> - <property name="receives-default">False</property> - </object> - </child> - <child internal-child="minus_button"> - <object class="GtkButton" id="volumebutton-minus_button2"> - <property name="can-focus">False</property> - <property name="receives-default">False</property> - </object> - </child> - <style> - <class name="chatcontrol-actionbar-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkVolumeButton" id="sound_hscale"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="focus-on-click">False</property> - <property name="receives-default">True</property> - <property name="has-tooltip">True</property> - <property name="relief">none</property> - <property name="orientation">vertical</property> - <property name="size">menu</property> - <property name="adjustment">adjustment1</property> - <property name="icons">audio-volume-muted-symbolic -audio-volume-low-symbolic -audio-volume-medium-symbolic -audio-volume-high-symbolic</property> - <property name="use-symbolic">False</property> - <child internal-child="plus_button"> - <object class="GtkButton" id="volumebutton-plus_button1"> - <property name="can-focus">False</property> - <property name="receives-default">False</property> - </object> - </child> - <child internal-child="minus_button"> - <object class="GtkButton" id="volumebutton-minus_button1"> - <property name="can-focus">False</property> - <property name="receives-default">False</property> - </object> - </child> - <style> - <class name="chatcontrol-actionbar-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkMenuButton" id="dtmf_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="relief">none</property> - <property name="direction">up</property> - <property name="popover">dtmf_popover</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">input-dialpad-symbolic</property> - <property name="icon_size">1</property> - </object> - </child> - <style> - <class name="chatcontrol-actionbar-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">4</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack-type">end</property> - <property name="position">1</property> - </packing> - </child> - <child> - <object class="GtkButton" id="av_cam_button"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes">Turn Camera on</property> - <property name="relief">none</property> - <signal name="clicked" handler="_on_video" swapped="no"/> - <child> - <object class="GtkImage" id="av_cam_image"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">feather-camera-symbolic</property> - </object> - </child> - <style> - <class name="chatcontrol-actionbar-button"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack-type">end</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="resize">True</property> - <property name="shrink">True</property> - </packing> + <placeholder/> </child> </object> <packing>
===================================== gajim/gtk/call_widget.py ===================================== @@ -0,0 +1,522 @@ +# This file is part of Gajim. +# +# Gajim 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 3 only. +# +# Gajim 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. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from typing import Optional # pylint: disable=unused-import + +import logging + +from gi.repository import GObject +from gi.repository import Gtk + +from nbxmpp.namespaces import Namespace + +from gajim.common import app +from gajim.common.const import JingleState +from gajim.common.helpers import allow_showing_notification +from gajim.common.i18n import _ + +from .gstreamer import create_gtk_widget +from .util import get_builder + +log = logging.getLogger('gajim.gui.call_widget') + + +class CallWidget(Gtk.Box): + + __gsignals__ = { + 'incoming-call': ( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, + (object, ) + ), + 'call-ended': ( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, + () + ), + } + + def __init__(self, account, contact): + Gtk.Box.__init__(self) + self.set_no_show_all(True) + + self._account = account + self._client = app.get_client(account) + self._contact = contact + + # Helper for upgrading voice call to voice + video without + # having to show a new call row + self._incoming_video_event = None + + self._jingle = { + 'audio': JingleObject( + JingleState.NULL, + self._update_audio), + 'video': JingleObject( + JingleState.NULL, + self._update_video), + } + self._video_widget_other = None + self._video_widget_self = None + + self._detect_av() + + self._ui = get_builder('call_widget.ui') + self.add(self._ui.av_box) + self.connect('destroy', self._on_destroy) + + dtmf = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'] + for key in dtmf: + button = self._ui.get_object(key + '_button') + button.connect('pressed', self._on_num_button_pressed, key) + button.connect('released', self._on_num_button_released) + + self._ui.connect_signals(self) + + def _on_destroy(self, *args): + for jingle_type in ('audio', 'video'): + self._close_jingle_content(jingle_type, shutdown=True) + self._jingle.clear() + + def _detect_av(self): + if (self._contact.supports(Namespace.JINGLE_ICE_UDP) and + app.is_installed('FARSTREAM') and self._contact.jid.resource): + self._jingle['audio'].available = self._contact.supports( + Namespace.JINGLE_RTP_AUDIO) + self._jingle['video'].available = self._contact.supports( + Namespace.JINGLE_RTP_VIDEO) + else: + if (self._jingle['audio'].available or + self._jingle['video'].available): + self._stop_jingle() + self._jingle['audio'].available = False + self._jingle['video'].available = False + + def get_jingle_available(self, content_type): + return self._jingle[content_type].available + + def _on_call_with_mic(self, _button): + self._on_jingle_button_toggled(['audio']) + self._ui.av_start_box.hide() + + def _on_call_with_mic_and_cam(self, _button): + self._on_jingle_button_toggled(['audio', 'video']) + self._ui.av_start_box.hide() + + def _on_answer_video_clicked(self, button): + self.accept_call(self._incoming_video_event) + self._incoming_video_event = None + button.hide() + + def _on_end_call_clicked(self, _widget): + self._close_jingle_content('audio') + self._close_jingle_content('video') + self._ui.jingle_audio_state.set_no_show_all(True) + self._ui.jingle_audio_state.hide() + self.set_no_show_all(True) + self.hide() + self.emit('call-ended') + + def _on_video(self, *args): + self._on_jingle_button_toggled(['video']) + + def _on_jingle_button_toggled(self, jingle_types): + if all(item in jingle_types for item in ['audio', 'video']): + # Both 'audio' and 'video' in jingle_types + sid = self._client.get_module('Jingle').start_audio_video( + self._contact.jid) + self._set_jingle_state('audio', JingleState.CONNECTING, sid) + self._set_jingle_state('video', JingleState.CONNECTING, sid) + return + + if 'audio' in jingle_types: + if self._jingle['audio'].state != JingleState.NULL: + self._close_jingle_content('audio') + else: + sid = self._client.get_module('Jingle').start_audio( + self._contact.jid) + self._set_jingle_state('audio', JingleState.CONNECTING, sid) + + if 'video' in jingle_types: + if self._jingle['video'].state != JingleState.NULL: + self._close_jingle_content('video') + else: + sid = self._client.get_module('Jingle').start_video( + self._contact.jid) + self._set_jingle_state('video', JingleState.CONNECTING, sid) + + def _on_num_button_pressed(self, _widget, num): + self._get_audio_content().start_dtmf(num) + + def _on_num_button_released(self, _released): + self._get_audio_content().stop_dtmf() + + def _on_mic_volume_changed(self, _widget, value): + self._get_audio_content().set_mic_volume(value / 100) + app.settings.set('audio_input_volume', int(value)) + + def _on_output_volume_changed(self, _widget, value): + self._get_audio_content().set_out_volume(value / 100) + app.settings.set('audio_output_volume', int(value)) + + def process_event(self, event): + if event.name == 'jingle-request-received': + self._on_jingle_request(event) + if event.name == 'jingle-connected-received': + self._on_jingle_connected(event) + if event.name == 'jingle-disconnected-received': + self._on_jingle_disconnected(event) + if event.name == 'jingle-error': + self._on_jingle_error(event) + + def _on_jingle_request(self, event): + content_types = [] + for item in event.contents: + content_types.append(item.media) + if 'audio' in content_types: + self._set_jingle_state( + 'audio', + JingleState.CONNECTION_RECEIVED, + event.sid) + if 'video' in content_types: + self._set_jingle_state( + 'video', + JingleState.CONNECTION_RECEIVED, + event.sid) + + if self._jingle['audio'].state in ( + JingleState.CONNECTED, + JingleState.CONNECTING) and 'video' in content_types: + self._incoming_video_event = event + else: + # There is no voice call running running yet + self.emit('incoming-call', event) + + if allow_showing_notification(self._account): + heading = _('Incoming Call') + contact = self._client.get_module('Contacts').get_contact( + event.jid) + text = _('%s is calling') % contact.name + app.notification.popup( + heading, + event.jid, + self._account, + 'jingle-incoming', + icon_name='call-start-symbolic', + title=heading, + text=text) + + def _on_jingle_connected(self, event): + session = self._client.get_module('Jingle').get_jingle_session( + event.fjid, event.sid) + + if event.media == 'audio': + content = session.get_content('audio') + self._set_jingle_state( + 'audio', + JingleState.CONNECTED, + event.sid) + if event.media == 'video': + content = session.get_content('video') + self._set_jingle_state( + 'video', + JingleState.CONNECTED, + event.sid) + + if not session.accepted: + session.approve_session() + for content in event.media: + session.approve_content(content) + + def _on_jingle_disconnected(self, event): + if event.media is None: + self._stop_jingle(sid=event.sid, reason=event.reason) + if event.media == 'audio': + self._set_jingle_state( + 'audio', + JingleState.NULL, + sid=event.sid, + reason=event.reason) + if event.media == 'video': + self._set_jingle_state( + 'video', + JingleState.NULL, + sid=event.sid, + reason=event.reason) + + def _on_jingle_error(self, event): + if event.sid == self._jingle['audio'].sid: + self._set_jingle_state( + 'audio', + JingleState.ERROR, + reason=event.reason) + + def start_call(self): + audio_state = self._jingle['audio'].state + video_state = self._jingle['video'].state + if audio_state == JingleState.NULL and video_state == JingleState.NULL: + self.set_no_show_all(False) + self.show_all() + self._ui.jingle_audio_state.hide() + self._ui.av_start_box.show() + self._ui.av_start_mic_cam_button.set_sensitive( + self._jingle['video'].available) + self._ui.av_cam_button.set_sensitive(False) + + def accept_call(self, event): + session = self._client.get_module('Jingle').get_jingle_session( + event.fjid, event.sid) + if not session: + return + + audio = session.get_content('audio') + video = session.get_content('video') + + if audio and not audio.negotiated: + self._set_jingle_state( + 'audio', JingleState.CONNECTING, event.sid) + if video and not video.negotiated: + self._set_jingle_state( + 'video', JingleState.CONNECTING, event.sid) + + if not session.accepted: + session.approve_session() + + for item in event.contents: + session.approve_content(item.media) + + def reject_call(self, event): + session = self._client.get_module('Jingle').get_jingle_session( + event.fjid, event.sid) + if not session: + return + + if not session.accepted: + session.decline_session() + else: + for item in event.contents: + session.reject_content(item.media) + + def _update_audio(self): + audio_state = self._jingle['audio'].state + video_state = self._jingle['video'].state + if self._jingle['video'].available: + self._ui.av_cam_button.set_sensitive( + video_state not in ( + JingleState.CONNECTING, + JingleState.CONNECTED)) + + if audio_state == JingleState.NULL: + self._ui.audio_buttons_box.set_sensitive(False) + self._ui.jingle_audio_state.set_no_show_all(True) + self._ui.jingle_audio_state.hide() + self._ui.jingle_connection_state.set_text('') + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + + if video_state == JingleState.NULL: + self.set_no_show_all(True) + self.hide() + self.emit('call-ended') + else: + self._ui.jingle_connection_spinner.show() + self._ui.jingle_connection_spinner.start() + + if audio_state == JingleState.CONNECTING: + self.set_no_show_all(False) + self.show_all() + self._ui.jingle_connection_state.set_text(_('Calling…')) + self._ui.av_cam_button.set_sensitive(False) + + elif audio_state == JingleState.CONNECTION_RECEIVED: + self._ui.jingle_connection_state.set_text(_('Incoming Call')) + + elif audio_state == JingleState.CONNECTED: + self._ui.jingle_audio_state.set_no_show_all(False) + self._ui.jingle_audio_state.show() + self._ui.jingle_connection_state.set_text('') + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + if self._jingle['video'].available: + self._ui.av_cam_button.set_sensitive(True) + + input_vol = app.settings.get('audio_input_volume') + output_vol = app.settings.get('audio_output_volume') + self._ui.mic_hscale.set_value(max(min(input_vol, 100), 0)) + self._ui.sound_hscale.set_value(max(min(output_vol, 100), 0)) + self._ui.audio_buttons_box.set_sensitive(True) + + elif audio_state == JingleState.ERROR: + self._ui.jingle_audio_state.hide() + self._ui.jingle_connection_state.set_text( + _('Connection Error')) + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + + if not self._jingle['audio'].sid: + self._ui.audio_buttons_box.set_sensitive(False) + + def _update_video(self): + audio_state = self._jingle['audio'].state + video_state = self._jingle['video'].state + + if video_state == JingleState.NULL: + self._ui.video_box.set_no_show_all(True) + self._ui.video_box.hide() + self._ui.outgoing_viewport.set_no_show_all(True) + self._ui.outgoing_viewport.hide() + if self._video_widget_other: + self._video_widget_other.destroy() + if self._video_widget_self: + self._video_widget_self.destroy() + + if audio_state != JingleState.CONNECTED: + self._ui.jingle_connection_state.set_text('') + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + self._ui.av_cam_button.set_sensitive(True) + self._ui.av_cam_button.set_tooltip_text(_('Turn Camera on')) + self._ui.av_cam_image.set_from_icon_name( + 'feather-camera-symbolic', Gtk.IconSize.BUTTON) + if audio_state == JingleState.NULL: + self.set_no_show_all(True) + self.hide() + self.emit('call-ended') + else: + self._ui.jingle_connection_spinner.show() + self._ui.jingle_connection_spinner.start() + + if video_state == JingleState.CONNECTING: + self._ui.jingle_connection_state.set_text(_('Calling (Video)…')) + self.set_no_show_all(False) + self.show_all() + self._ui.av_cam_button.set_sensitive(False) + self._ui.av_cam_button.set_tooltip_text(_('Turn Camera off')) + self._ui.av_cam_image.set_from_icon_name( + 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) + + elif video_state == JingleState.CONNECTION_RECEIVED: + self._ui.jingle_connection_state.set_text( + _('Incoming Call (Video)')) + self._ui.answer_video_button.show() + self._ui.av_cam_button.set_sensitive(False) + self._ui.av_cam_button.set_tooltip_text(_('Turn Camera off')) + self._ui.av_cam_image.set_from_icon_name( + 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) + + elif video_state == JingleState.CONNECTED: + self._ui.video_box.set_no_show_all(False) + self._ui.video_box.show_all() + self._ui.answer_video_button.hide() + if app.settings.get('video_see_self'): + self._ui.outgoing_viewport.set_no_show_all(False) + self._ui.outgoing_viewport.show() + else: + self._ui.outgoing_viewport.set_no_show_all(True) + self._ui.outgoing_viewport.hide() + + sink_other, self._video_widget_other, _name = create_gtk_widget() + sink_self, self._video_widget_self, _name = create_gtk_widget() + self._ui.incoming_viewport.add(self._video_widget_other) + self._ui.outgoing_viewport.add(self._video_widget_self) + + session = self._client.get_module('Jingle').get_jingle_session( + self._contact.jid, self._jingle['video'].sid) + content = session.get_content('video') + content.do_setup(sink_self, sink_other) + + self._ui.jingle_connection_state.set_text('') + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + + self._ui.av_cam_button.set_sensitive(True) + self._ui.av_cam_button.set_tooltip_text(_('Turn Camera off')) + self._ui.av_cam_image.set_from_icon_name( + 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) + + elif video_state == JingleState.ERROR: + self._ui.jingle_connection_state.set_text( + _('Connection Error')) + self._ui.jingle_connection_spinner.stop() + self._ui.jingle_connection_spinner.hide() + + def _set_jingle_state(self, jingle_type: str, state: str, sid: str = None, + reason: str = None) -> None: + jingle = self._jingle[jingle_type] + if state in ( + JingleState.CONNECTING, + JingleState.CONNECTED, + JingleState.NULL, + JingleState.ERROR) and reason: + log.info('%s state: %s, reason: %s', jingle_type, state, reason) + + if state in (jingle.state, JingleState.ERROR): + return + + if (state == JingleState.NULL and jingle.sid not in (None, sid)): + return + + new_sid = None + if state == JingleState.NULL: + new_sid = None + if state in ( + JingleState.CONNECTION_RECEIVED, + JingleState.CONNECTING, + JingleState.CONNECTED): + new_sid = sid + + jingle.state = state + jingle.sid = new_sid + jingle.update() + + def _stop_jingle(self, sid=None, reason=None): + audio_sid = self._jingle['audio'].sid + video_sid = self._jingle['video'].sid + if audio_sid and sid in (audio_sid, None): + self._close_jingle_content('audio') + if video_sid and sid in (video_sid, None): + self._close_jingle_content('video') + + def _close_jingle_content(self, jingle_type: str, + shutdown: Optional[bool] = False) -> None: + jingle = self._jingle[jingle_type] + if not jingle.sid: + return + + session = self._client.get_module('Jingle').get_jingle_session( + self._contact.jid, jingle.sid) + if session: + content = session.get_content(jingle_type) + if content: + session.remove_content(content.creator, content.name) + + if not shutdown: + jingle.sid = None + jingle.state = JingleState.NULL + jingle.update() + + def _get_audio_content(self): + session = self._client.get_module('Jingle').get_jingle_session( + self._contact.jid, self._jingle['audio'].sid) + return session.get_content('audio') + + +class JingleObject: + __slots__ = ('sid', 'state', 'available', 'update') + + def __init__(self, state, update): + self.sid = None + self.state = state + self.available = False + self.update = update
===================================== gajim/gtk/controls/base.py ===================================== @@ -277,6 +277,23 @@ def _get_action(self, name): return app.window.lookup_action(name + self.control_id)
def process_event(self, event): + if event.account != self.account: + return + + jid = self.contact.jid.bare + if event.jid != jid: + return + + jingle_av_events = [ + 'jingle-request-received', + 'jingle-connected-received', + 'jingle-disconnected-received', + 'jingle-error-received' + ] + if self.is_chat and event.name in jingle_av_events: + self._process_jingle_av_event(event) + return + method_name = event.name.replace('-', '_') method_name = f'_on_{method_name}' getattr(self, method_name)(event)
===================================== gajim/gtk/controls/chat.py ===================================== @@ -25,7 +25,6 @@
from typing import ClassVar # pylint: disable=unused-import from typing import Type # pylint: disable=unused-import -from typing import Optional # pylint: disable=unused-import
import os import time @@ -50,13 +49,12 @@ from gajim.common.const import AvatarSize from gajim.common.const import KindConstant from gajim.common.const import PEPEventType -from gajim.common.const import JingleState
from gajim import gtkgui_helpers from gajim import gui_menu_builder from gajim import dialogs
-from gajim.gui.gstreamer import create_gtk_widget +from gajim.gui.call_widget import CallWidget from gajim.gui.dialogs import DialogButton from gajim.gui.dialogs import ConfirmationDialog from gajim.gui.util import get_cursor @@ -75,17 +73,6 @@ log = logging.getLogger('gajim.gui.controls.chat')
-class JingleObject: - __slots__ = ('sid', 'state', 'available', 'update') - - def __init__(self, state, update): - self.sid = None - self.state = state - self.available = False - self.update = update - - -################################################################################ class ChatControl(BaseControl): """ A control for standard 1-1 chat @@ -119,6 +106,14 @@ def __init__(self, account, jid): self.xml.sendfile_button.set_action_name( 'win.send-file-%s' % self.control_id)
+ self._call_widget = CallWidget(self.account, self.contact) + self._call_widget.connect('incoming-call', self._add_incoming_call) + self._call_widget.connect('call-ended', self._on_call_ended) + self.xml.paned1.add2(self._call_widget) + + self.conversation_view.connect('accept-call', self._on_accept_call) + self.conversation_view.connect('reject-call', self._on_reject_call) + # Menu for the HeaderBar self.control_menu = gui_menu_builder.get_singlechat_menu( self.control_id, self.account, self.contact.jid, self._type) @@ -126,18 +121,6 @@ def __init__(self, account, jid): # Settings menu self.xml.settings_menu.set_menu_model(self.control_menu)
- self.jingle = { - 'audio': JingleObject( - JingleState.NULL, - self.update_audio), - 'video': JingleObject( - JingleState.NULL, - self.update_video), - } - self._video_widget_other = None - self._video_widget_self = None - self.conversation_view.connect('call-answered', self._on_call_answered) - self.update_toolbar() self.update_all_pep_types() self._update_avatar() @@ -154,22 +137,6 @@ def __init__(self, account, jid): self.on_location_eventbox_leave_notify_event) self.handlers[id_] = widget
- for key in ('1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '0', '#'): - widget = self.xml.get_object(key + '_button') - id_ = widget.connect('pressed', self.on_num_button_pressed, key) - self.handlers[id_] = widget - id_ = widget.connect('released', self.on_num_button_released) - self.handlers[id_] = widget - - widget = self.xml.mic_hscale - id_ = widget.connect('value_changed', self.on_mic_hscale_value_changed) - self.handlers[id_] = widget - - widget = self.xml.sound_hscale - id_ = widget.connect('value_changed', - self.on_sound_hscale_value_changed) - self.handlers[id_] = widget - self.info_bar = Gtk.InfoBar() content_area = self.info_bar.get_content_area() self.info_bar_label = Gtk.Label() @@ -259,23 +226,11 @@ def update_actions(self): self._get_action('block-contact-').set_enabled( online and self._client.get_module('Blocking').supported)
- # Jingle AV detection - if (self.contact.supports(Namespace.JINGLE_ICE_UDP) and - app.is_installed('FARSTREAM') and self.contact.jid.resource): - self.jingle['audio'].available = self.contact.supports( - Namespace.JINGLE_RTP_AUDIO) - self.jingle['video'].available = self.contact.supports( - Namespace.JINGLE_RTP_VIDEO) - else: - if (self.jingle['audio'].available or - self.jingle['video'].available): - self.stop_jingle() - self.jingle['audio'].available = False - self.jingle['video'].available = False - - self._get_action(f'start-call-').set_enabled( - online and (self.jingle['audio'].available or - self.jingle['video'].available)) + # Jingle AV + audio_available = self._call_widget.get_jingle_available('audio') + video_available = self._call_widget.get_jingle_available('video') + self._get_action('start-call-').set_enabled( + online and (audio_available or video_available))
# Send message has_text = self.msg_textview.has_text() @@ -556,318 +511,23 @@ def _nec_ping(self, event): elif event.name == 'ping-error': self.add_info_message(event.error)
- # Jingle AV - def _on_start_call(self, *args): - audio_state = self.jingle['audio'].state - video_state = self.jingle['video'].state - if audio_state == JingleState.NULL and video_state == JingleState.NULL: - self.xml.av_box.set_no_show_all(False) - self.xml.av_box.show_all() - self.xml.jingle_audio_state.hide() - self.xml.av_start_box.show() - self.xml.av_start_mic_cam_button.set_sensitive( - self.jingle['video'].available) - self.xml.av_cam_button.set_sensitive(False) - - def _on_call_with_mic(self, _button): - self._on_jingle_button_toggled(['audio']) - self.xml.av_start_box.hide() - - def _on_call_with_mic_and_cam(self, _button): - self._on_jingle_button_toggled(['audio', 'video']) - self.xml.av_start_box.hide() - - def _on_video(self, *args): - self._on_jingle_button_toggled(['video']) - - def update_audio(self): - self.update_actions() - - audio_state = self.jingle['audio'].state - video_state = self.jingle['video'].state - if self.jingle['video'].available: - self.xml.av_cam_button.set_sensitive( - video_state not in ( - JingleState.CONNECTING, - JingleState.CONNECTED)) - - if audio_state == JingleState.NULL: - self.xml.audio_buttons_box.set_sensitive(False) - self.xml.jingle_audio_state.set_no_show_all(True) - self.xml.jingle_audio_state.hide() - self.xml.jingle_connection_state.set_text('') - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - - if video_state == JingleState.NULL: - self.xml.av_box.set_no_show_all(True) - self.xml.av_box.hide() - else: - self.xml.jingle_connection_spinner.show() - self.xml.jingle_connection_spinner.start() - - if audio_state == JingleState.CONNECTING: - self.xml.av_box.set_no_show_all(False) - self.xml.av_box.show_all() - self.xml.jingle_connection_state.set_text( - _('Calling…')) - self.xml.av_cam_button.set_sensitive(False) - - elif audio_state == JingleState.CONNECTION_RECEIVED: - self.xml.jingle_connection_state.set_text( - _('Incoming Call')) - - elif audio_state == JingleState.CONNECTED: - self.xml.jingle_audio_state.set_no_show_all(False) - self.xml.jingle_audio_state.show() - self.xml.jingle_connection_state.set_text('') - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - if self.jingle['video'].available: - self.xml.av_cam_button.set_sensitive(True) - - input_vol = app.settings.get('audio_input_volume') - output_vol = app.settings.get('audio_output_volume') - self.xml.mic_hscale.set_value(max(min(input_vol, 100), 0)) - self.xml.sound_hscale.set_value(max(min(output_vol, 100), 0)) - self.xml.audio_buttons_box.set_sensitive(True) - - elif audio_state == JingleState.ERROR: - self.xml.jingle_audio_state.hide() - self.xml.jingle_connection_state.set_text( - _('Connection Error')) - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - - if not self.jingle['audio'].sid: - self.xml.audio_buttons_box.set_sensitive(False) - - def update_video(self): - self.update_actions() - - audio_state = self.jingle['audio'].state - video_state = self.jingle['video'].state - - if video_state == JingleState.NULL: - self.xml.video_box.set_no_show_all(True) - self.xml.video_box.hide() - self.xml.outgoing_viewport.set_no_show_all(True) - self.xml.outgoing_viewport.hide() - if self._video_widget_other: - self._video_widget_other.destroy() - if self._video_widget_self: - self._video_widget_self.destroy() - - if audio_state != JingleState.CONNECTED: - self.xml.jingle_connection_state.set_text('') - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - self.xml.av_cam_button.set_sensitive(True) - self.xml.av_cam_button.set_tooltip_text(_('Turn Camera on')) - self.xml.av_cam_image.set_from_icon_name( - 'feather-camera-symbolic', Gtk.IconSize.BUTTON) - if audio_state == JingleState.NULL: - self.xml.av_box.set_no_show_all(True) - self.xml.av_box.hide() - else: - self.xml.jingle_connection_spinner.show() - self.xml.jingle_connection_spinner.start() - - if video_state == JingleState.CONNECTING: - self.xml.jingle_connection_state.set_text(_('Calling (Video)…')) - self.xml.av_box.set_no_show_all(False) - self.xml.av_box.show_all() - self.xml.av_cam_button.set_sensitive(False) - self.xml.av_cam_button.set_tooltip_text(_('Turn Camera off')) - self.xml.av_cam_image.set_from_icon_name( - 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) - - elif video_state == JingleState.CONNECTION_RECEIVED: - self.xml.jingle_connection_state.set_text( - _('Incoming Call (Video)')) - self.xml.av_cam_button.set_sensitive(False) - self.xml.av_cam_button.set_tooltip_text(_('Turn Camera off')) - self.xml.av_cam_image.set_from_icon_name( - 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) - - elif video_state == JingleState.CONNECTED: - self.xml.video_box.set_no_show_all(False) - self.xml.video_box.show_all() - if app.settings.get('video_see_self'): - self.xml.outgoing_viewport.set_no_show_all(False) - self.xml.outgoing_viewport.show() - else: - self.xml.outgoing_viewport.set_no_show_all(True) - self.xml.outgoing_viewport.hide() - - sink_other, self._video_widget_other, _name = create_gtk_widget() - sink_self, self._video_widget_self, _name = create_gtk_widget() - self.xml.incoming_viewport.add(self._video_widget_other) - self.xml.outgoing_viewport.add(self._video_widget_self) - - session = self._client.get_module('Jingle').get_jingle_session( - self.contact.jid, self.jingle['video'].sid) - content = session.get_content('video') - content.do_setup(sink_self, sink_other) - - self.xml.jingle_connection_state.set_text('') - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - - self.xml.av_cam_button.set_sensitive(True) - self.xml.av_cam_button.set_tooltip_text(_('Turn Camera off')) - self.xml.av_cam_image.set_from_icon_name( - 'feather-camera-off-symbolic', Gtk.IconSize.BUTTON) - - elif video_state == JingleState.ERROR: - self.xml.jingle_connection_state.set_text( - _('Connection Error')) - self.xml.jingle_connection_spinner.stop() - self.xml.jingle_connection_spinner.hide() - - def set_jingle_state(self, jingle_type: str, state: str, sid: str = None, - reason: str = None) -> None: - jingle = self.jingle[jingle_type] - if state in ( - JingleState.CONNECTING, - JingleState.CONNECTED, - JingleState.NULL, - JingleState.ERROR) and reason: - log.info('%s state: %s, reason: %s', jingle_type, state, reason) - - if state in (jingle.state, JingleState.ERROR): - return - - if (state == JingleState.NULL and jingle.sid not in (None, sid)): - return - - new_sid = None - if state == JingleState.NULL: - new_sid = None - if state in ( - JingleState.CONNECTION_RECEIVED, - JingleState.CONNECTING, - JingleState.CONNECTED): - new_sid = sid - - jingle.state = state - jingle.sid = new_sid - jingle.update() - - def stop_jingle(self, sid=None, reason=None): - audio_sid = self.jingle['audio'].sid - video_sid = self.jingle['video'].sid - if audio_sid and sid in (audio_sid, None): - self.close_jingle_content('audio') - if video_sid and sid in (video_sid, None): - self.close_jingle_content('video') - - def close_jingle_content(self, jingle_type: str, - shutdown: Optional[bool] = False) -> None: - jingle = self.jingle[jingle_type] - if not jingle.sid: - return - - session = self._client.get_module('Jingle').get_jingle_session( - self.contact.jid, jingle.sid) - if session: - content = session.get_content(jingle_type) - if content: - session.remove_content(content.creator, content.name) - - if not shutdown: - jingle.sid = None - jingle.state = JingleState.NULL - jingle.update() - - def _on_end_call_clicked(self, _widget): - self.close_jingle_content('audio') - self.close_jingle_content('video') - self.xml.jingle_audio_state.set_no_show_all(True) - self.xml.jingle_audio_state.hide() - self.xml.av_box.set_no_show_all(True) - self.xml.av_box.hide() - - def _on_jingle_button_toggled(self, jingle_types): - if all(item in jingle_types for item in ['audio', 'video']): - # Both 'audio' and 'video' in jingle_types - sid = self._client.get_module('Jingle').start_audio_video( - self.contact.jid) - self.set_jingle_state('audio', JingleState.CONNECTING, sid) - self.set_jingle_state('video', JingleState.CONNECTING, sid) - return - - if 'audio' in jingle_types: - if self.jingle['audio'].state != JingleState.NULL: - self.close_jingle_content('audio') - else: - sid = self._client.get_module('Jingle').start_audio( - self.contact.jid) - self.set_jingle_state('audio', JingleState.CONNECTING, sid) - - if 'video' in jingle_types: - if self.jingle['video'].state != JingleState.NULL: - self.close_jingle_content('video') - else: - sid = self._client.get_module('Jingle').start_video( - self.contact.jid) - self.set_jingle_state('video', JingleState.CONNECTING, sid) - - def _get_audio_content(self): - session = self._client.get_module('Jingle').get_jingle_session( - self.contact.jid, self.jingle['audio'].sid) - return session.get_content('audio') - - def on_num_button_pressed(self, _widget, num): - self._get_audio_content().start_dtmf(num) - - def on_num_button_released(self, _released): - self._get_audio_content().stop_dtmf() - - def on_mic_hscale_value_changed(self, _widget, value): - self._get_audio_content().set_mic_volume(value / 100) - app.settings.set('audio_input_volume', int(value)) - - def on_sound_hscale_value_changed(self, _widget, value): - self._get_audio_content().set_out_volume(value / 100) - app.settings.set('audio_output_volume', int(value)) - - def _on_call_answered(self, _view, action, event): - if action == 'accept': - session = self._client.get_module('Jingle').get_jingle_session( - event.fjid, event.sid) - if not session: - return - - audio = session.get_content('audio') - video = session.get_content('video') - - if audio and not audio.negotiated: - self.set_jingle_state( - 'audio', JingleState.CONNECTING, event.sid) - if video and not video.negotiated: - self.set_jingle_state( - 'video', JingleState.CONNECTING, event.sid) + # Jingle AV calls + def _on_start_call(self): + self._call_widget.start_call()
- if not session.accepted: - session.approve_session() + def _process_jingle_av_event(self, event): + self._call_widget.process_event(event)
- for item in event.contents: - session.approve_content(item.media) + def _on_accept_call(self, _view, event): + self._call_widget.accept_call(event)
- if action == 'reject': - session = self._client.get_module('Jingle').get_jingle_session( - event.fjid, event.sid) - if not session: - return + def _on_reject_call(self, _view, event): + self._call_widget.reject_call(event)
- if not session.accepted: - session.decline_session() - else: - for item in event.contents: - session.reject_content(item.media) + def _on_call_ended(self, _call_widget): + self.conversation_view.update_call_rows()
- def add_incoming_call(self, event): + def _add_incoming_call(self, _call_widget, event): self.add_call_message(time.time(), '', event=event)
def on_location_eventbox_button_release_event(self, _widget, _event): @@ -1023,10 +683,6 @@ def shutdown(self): self._client.get_module('Chatstate').set_chatstate( self.contact, Chatstate.GONE)
- for jingle_type in ('audio', 'video'): - self.close_jingle_content(jingle_type, shutdown=True) - self.jingle.clear() - super(ChatControl, self).shutdown() app.check_finalize(self)
===================================== gajim/gtk/conversation/rows/call.py ===================================== @@ -45,9 +45,10 @@ def __init__(self, account, contact, timestamp, text, event=None): Gtk.IconSize.MENU) self.grid.attach(icon, 1, 0, 1, 1)
- self._label = SimpleLabel() - self._label.set_text(text) - self.grid.attach(self._label, 2, 0, 1, 1) + label = SimpleLabel() + label.get_style_context().add_class('dim-label') + label.set_text(text) + self.grid.attach(label, 2, 0, 1, 1) else: self._prepare_incoming_call()
@@ -59,21 +60,40 @@ def __init__(self, account, contact, timestamp, text, event=None):
self.show_all()
+ def update(self): + if self._event is None: + return + + self._call_box.destroy() + + icon = Gtk.Image.new_from_icon_name('call-start-symbolic', + Gtk.IconSize.MENU) + self.grid.attach(icon, 1, 0, 1, 1) + + label = SimpleLabel() + label.get_style_context().add_class('dim-label') + text = _('%s called you') % self._contact.name + label.set_text(text) + self.grid.attach(label, 2, 0, 1, 1) + self.show_all() + self._event = None + def _on_accept(self, button): button.set_sensitive(False) self._reject_button.set_sensitive(False) - self.get_parent().on_call_answered('accept', self._event) + self.get_parent().accept_call(self._event)
def _on_reject(self, button): button.set_sensitive(False) self._accept_button.set_sensitive(False) - self.get_parent().on_call_answered('reject', self._event) + self.get_parent().reject_call(self._event)
def _prepare_incoming_call(self): - call_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) - call_box.set_size_request(350, -1) - call_box.get_style_context().add_class('conversation-call-box') - call_box.get_style_context().add_class('gajim-call-message') + self._call_box = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=6) + self._call_box.set_size_request(350, -1) + self._call_box.get_style_context().add_class('conversation-call-box') + self._call_box.get_style_context().add_class('gajim-call-message')
scale = self.get_scale_factor() avatar = self._contact.get_avatar( @@ -81,7 +101,7 @@ def _prepare_incoming_call(self): scale, add_show=False) avatar_image = Gtk.Image.new_from_surface(avatar) - call_box.add(avatar_image) + self._call_box.add(avatar_image)
content_types = [] for item in self._event.contents: @@ -95,7 +115,7 @@ def _prepare_incoming_call(self): label.get_style_context().add_class('bold') label.set_max_width_chars(40) label.set_line_wrap(True) - call_box.add(label) + self._call_box.add(label)
self._accept_button = Gtk.Button.new_with_label(_('Accept Call')) self._accept_button.connect('clicked', self._on_accept) @@ -107,6 +127,6 @@ def _prepare_incoming_call(self): button_box.set_halign(Gtk.Align.CENTER) button_box.add(self._accept_button) button_box.add(self._reject_button) - call_box.add(button_box) + self._call_box.add(button_box)
- self.grid.attach(call_box, 1, 0, 1, 1) + self.grid.attach(self._call_box, 1, 0, 1, 1)
===================================== gajim/gtk/conversation/view.py ===================================== @@ -48,10 +48,15 @@ class ConversationView(Gtk.ListBox): None, (str, ) ), - 'call-answered': ( + 'accept-call': ( GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, None, - (str, object) + (object, ) + ), + 'reject-call': ( + GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, + None, + (object, ) ), }
@@ -376,6 +381,11 @@ def iter_rows(self): for row in self.get_children(): yield row
+ def update_call_rows(self): + for row in self.get_children(): + if row.type == 'call': + row.update() + def set_read_marker(self, id_): if id_ is None: self._read_marker_row.hide() @@ -423,8 +433,11 @@ def show_error(self, id_, error): def on_quote(self, text): self.emit('quote', text)
- def on_call_answered(self, action, event): - self.emit('call-answered', action, event) + def accept_call(self, event): + self.emit('accept-call', event) + + def reject_call(self, event): + self.emit('reject-call', event)
def _on_contact_setting_changed(self, *args): self.invalidate_filter()
===================================== gajim/gtk/main.py ===================================== @@ -74,6 +74,10 @@ def __init__(self): ('muc-self-presence', ged.GUI1, self._on_event), ('muc-voice-request', ged.GUI1, self._on_event), ('muc-disco-update', ged.GUI1, self._on_event), + ('jingle-request-received', ged.GUI1, self._on_event), + ('jingle-connected-received', ged.GUI1, self._on_event), + ('jingle-disconnected-received', ged.GUI1, self._on_event), + ('jingle-error-received', ged.GUI1, self._on_event), ('our-show', ged.GUI1, self._on_our_show), ('signed-in', ged.GUI1, self._on_signed_in), ('account-enabled', ged.GUI1, self._on_account_enabled), @@ -442,7 +446,7 @@ def _load_chats(self):
def _on_event(self, event): if event.name == 'caps-update': - #TODO + # TODO return
if event.name == 'update-roster-avatar': @@ -457,6 +461,13 @@ def _on_event(self, event): else: jid = event.properties.jid.new_as_bare() self.add_chat(event.account, jid, 'contact') + elif event.name == 'jingle-request-received': + content_types = [] + for item in event.contents: + content_types.append(item.media) + if 'audio' in content_types or 'video' in content_types: + # AV Call received, open chat control + self.add_chat(event.account, event.jid, 'contact') else: # No chat is open, dont handle any gui events return
===================================== gajim/gui_interface.py ===================================== @@ -223,10 +223,6 @@ def _create_core_handlers_list(self): 'file-send-error': [self.handle_event_file_send_error], 'file-request-error': [self.handle_event_file_request_error], 'file-request-received': [self.handle_event_file_request], - 'jingle-connected-received': [self.handle_event_jingle_connected], - 'jingle-disconnected-received': [self.handle_event_jingle_disconnected], - 'jingle-error-received': [self.handle_event_jingle_error], - 'jingle-request-received': [self.handle_event_jingle_incoming], 'jingle-ft-cancelled-received': [self.handle_event_jingleft_cancel], } # pylint: enable=line-too-long @@ -1021,124 +1017,6 @@ def _popup_ft_result(self, account, jid, file_props): title=event_type, text=txt)
- # Jingle AV handling - @staticmethod - def handle_event_jingle_incoming(event): - # ('JINGLE_INCOMING', conn, fjid, sid, resource, jingle session, - # tuple-of-contents==(type, data...)) - # TODO: conditional blocking if peer is not in roster - - account = event.conn.name - - content_types = [] - for item in event.contents: - content_types.append(item.media) - - if 'audio' not in content_types and 'video' not in content_types: - return - - ctrl = app.window.get_control(account, event.jid) - if ctrl: - if 'audio' in content_types: - ctrl.set_jingle_state( - 'audio', - JingleState.CONNECTION_RECEIVED, - event.sid) - if 'video' in content_types: - ctrl.set_jingle_state( - 'video', - JingleState.CONNECTION_RECEIVED, - event.sid) - ctrl.add_incoming_call(event) - - if helpers.allow_popup_window(account): - def _prepare_control(): - ctrl = app.window.get_control(account, event.jid) - ctrl.add_incoming_call(event) - - if not ctrl: - app.window.add_chat( - account, event.jid, 'contact', select=True) - GLib.idle_add(_prepare_control) - return - - if helpers.allow_showing_notification(account): - heading = _('Incoming Call') - client = app.get_client(account) - contact = client.get_module('Contacts').get_contact(event.jid) - text = _('%s is calling') % contact.name - app.notification.popup( - heading, - event.jid, - account, - 'jingle-incoming', - icon_name='call-start-symbolic', - title=heading, - text=text) - - @staticmethod - def handle_event_jingle_connected(event): - # ('JINGLE_CONNECTED', account, (peerjid, sid, media)) - if event.media in ('audio', 'video'): - account = event.conn.name - ctrl = app.window.get_control(account, event.jid) - if ctrl: - client = app.get_client(account) - session = client.get_module('Jingle').get_jingle_session( - event.fjid, event.sid) - - if event.media == 'audio': - content = session.get_content('audio') - ctrl.set_jingle_state( - 'audio', - JingleState.CONNECTED, - event.sid) - if event.media == 'video': - content = session.get_content('video') - ctrl.set_jingle_state( - 'video', - JingleState.CONNECTED, - event.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 event.media: - session.approve_content(content) - - @staticmethod - def handle_event_jingle_disconnected(event): - # ('JINGLE_DISCONNECTED', account, (peerjid, sid, reason)) - account = event.conn.name - ctrl = app.window.get_control(account, event.jid) - if ctrl: - if event.media is None: - ctrl.stop_jingle(sid=event.sid, reason=event.reason) - if event.media == 'audio': - ctrl.set_jingle_state( - 'audio', - JingleState.NULL, - sid=event.sid, - reason=event.reason) - if event.media == 'video': - ctrl.set_jingle_state( - 'video', - JingleState.NULL, - sid=event.sid, - reason=event.reason) - - @staticmethod - def handle_event_jingle_error(event): - # ('JINGLE_ERROR', account, (peerjid, sid, reason)) - account = event.conn.name - ctrl = app.window.get_control(account, event.jid) - if ctrl and event.sid == ctrl.jingle['audio'].sid: - ctrl.set_jingle_state( - 'audio', - JingleState.ERROR, - reason=event.reason) - def send_httpupload(self, chat_control, path=None): if path is not None: self._send_httpupload(chat_control, path)
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/2a0fc69b4a73a5711e8f70e7b515a011b...