Philipp Hörist pushed to branch master at gajim / gajim
Commits: 5777b429 by Emmanuel Gil Peyrot at 2020-03-17T19:32:39+01:00 ChatControl: Remove all getattr() and setattr()
These were due to Jingle properties existing for both audio and video, replace them with the obvious dict solution.
- - - - - 3c85325c by Emmanuel Gil Peyrot at 2020-03-17T19:33:00+01:00 Jingle video: Replace X11 specifics with GTK+
This uses the same mechanism originally added for preferences, so that video display is handled by a GTK+ widget instead of some X11 dark magic.
This makes the self video display properly (!) during a Jingle call, but still with random freezes and no remote video being displayed, those fixes will be for a latter commit.
- - - - -
8 changed files:
- gajim/chat_control.py - gajim/common/jingle_rtp.py - gajim/common/modules/jingle.py - gajim/data/gui/chat_control.ui - gajim/dialogs.py - + gajim/gtk/gstreamer.py - gajim/gtk/preferences.py - gajim/gui_interface.py
Changes:
===================================== gajim/chat_control.py ===================================== @@ -79,6 +79,20 @@ from gajim.command_system.implementation.hosts import ChatCommands from gajim.command_system.framework import CommandHost # pylint: disable=unused-import from gajim.chat_control_base import ChatControlBase
+ +class JingleState: + __slots__ = ('sid', 'state', 'available', 'banner_image', 'action', 'set_state', 'update') + + def __init__(self, state, banner_image, set_state, update): + self.sid = None + self.state = state + self.available = False + self.banner_image = banner_image + self.action = None + self.set_state = set_state + self.update = update + + ################################################################################ class ChatControl(ChatControlBase): """ @@ -126,12 +140,10 @@ class ChatControl(ChatControlBase): # Settings menu self.xml.settings_menu.set_menu_model(self.control_menu)
- self.audio_sid = None - self.audio_state = self.JINGLE_STATE_NULL - self.audio_available = False - self.video_sid = None - self.video_state = self.JINGLE_STATE_NULL - self.video_available = False + self.jingle = { + 'audio': JingleState(self.JINGLE_STATE_NULL, self.xml.audio_banner_image, self.set_audio_state, self.update_audio), + 'video': JingleState(self.JINGLE_STATE_NULL, self.xml.video_banner_image, self.set_video_state, self.update_video), + }
self.update_toolbar() self.update_all_pep_types() @@ -254,17 +266,19 @@ class ChatControl(ChatControlBase): act.connect("activate", func) self.parent_win.window.add_action(act)
- self.audio_action = Gio.SimpleAction.new_stateful( + audio = self.jingle['audio'] + audio.action = Gio.SimpleAction.new_stateful( 'toggle-audio-' + self.control_id, None, GLib.Variant.new_boolean(False)) - self.audio_action.connect('change-state', self._on_audio) - self.parent_win.window.add_action(self.audio_action) + audio.action.connect('change-state', self._on_audio) + self.parent_win.window.add_action(audio.action)
- self.video_action = Gio.SimpleAction.new_stateful( + video = self.jingle['video'] + video.action = Gio.SimpleAction.new_stateful( 'toggle-video-' + self.control_id, None, GLib.Variant.new_boolean(False)) - self.video_action.connect('change-state', self._on_video) - self.parent_win.window.add_action(self.video_action) + video.action.connect('change-state', self._on_video) + self.parent_win.window.add_action(video.action)
default_chatstate = app.config.get('send_chatstate_default') chatstate = app.config.get_per( @@ -294,11 +308,11 @@ class ChatControl(ChatControlBase):
# Audio win.lookup_action('toggle-audio-' + self.control_id).set_enabled( - online and self.audio_available) + online and self.jingle['audio'].available)
# Video win.lookup_action('toggle-video-' + self.control_id).set_enabled( - online and self.video_available) + online and self.jingle['video'].available)
# Send file (HTTP File Upload) httpupload = win.lookup_action( @@ -419,15 +433,17 @@ class ChatControl(ChatControlBase): _('This contact does not support HTML'))
# Jingle detection + jingle_audio = self.jingle['audio'] + jingle_video = self.jingle['video'] if self.contact.supports(NS_JINGLE_ICE_UDP) and \ app.is_installed('FARSTREAM') and self.contact.resource: - self.audio_available = self.contact.supports(NS_JINGLE_RTP_AUDIO) - self.video_available = self.contact.supports(NS_JINGLE_RTP_VIDEO) + jingle_audio.available = self.contact.supports(NS_JINGLE_RTP_AUDIO) + jingle_video.available = self.contact.supports(NS_JINGLE_RTP_VIDEO) else: - if self.video_available or self.audio_available: + if jingle_video.available or jingle_audio.available: self.stop_jingle() - self.video_available = False - self.audio_available = False + jingle_video.available = False + jingle_audio.available = False
def update_all_pep_types(self): self._update_pep(PEPEventType.LOCATION) @@ -630,11 +646,10 @@ class ChatControl(ChatControlBase): elif event.name == 'ping-error': self.add_info_message(_('Error.'))
- def _update_jingle(self, jingle_type): - if jingle_type not in ('audio', 'video'): - return - banner_image = getattr(self, '_' + jingle_type + '_banner_image') - state = getattr(self, jingle_type + '_state') + def _update_jingle(self, jingle_type: str) -> None: + jingle = self.jingle[jingle_type] + banner_image = jingle.banner_image + state = jingle.state if state == self.JINGLE_STATE_NULL: banner_image.hide() else: @@ -656,7 +671,7 @@ class ChatControl(ChatControlBase): def update_audio(self): self._update_jingle('audio') hbox = self.xml.audio_buttons_hbox - if self.audio_state == self.JINGLE_STATE_CONNECTED: + if self.jingle['audio'].state == self.JINGLE_STATE_CONNECTED: # Set volume from config input_vol = app.config.get('audio_input_volume') output_vol = app.config.get('audio_output_volume') @@ -667,7 +682,7 @@ class ChatControl(ChatControlBase): # Show vbox hbox.set_no_show_all(False) hbox.show_all() - elif not self.audio_sid: + elif not self.jingle['audio'].sid: hbox.set_no_show_all(True) hbox.hide()
@@ -687,15 +702,17 @@ class ChatControl(ChatControlBase): # update MessageWindow._controls self.parent_win.change_jid(self.account, old_full_jid, new_full_jid)
- def stop_jingle(self, sid=None, _reason=None): - if self.audio_sid and sid in (self.audio_sid, None): + 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 self.video_sid and sid in (self.video_sid, None): + if video_sid and sid in (video_sid, None): self.close_jingle_content('video')
- def _set_jingle_state(self, jingle_type, state, sid=None, reason=None): - if jingle_type not in ('audio', 'video'): - return + 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 ('connecting', 'connected', 'stop', 'error') and reason: info = _('%(type)s state : %(state)s, reason: %(reason)s') % { 'type': jingle_type.capitalize(), @@ -710,25 +727,25 @@ class ChatControl(ChatControlBase): 'error': self.JINGLE_STATE_ERROR}
jingle_state = states[state] - if (getattr(self, jingle_type + '_state') == jingle_state or - state == 'error'): + if jingle.state == jingle_state or state == 'error': return
- if (state == 'stop' and - getattr(self, jingle_type + '_sid') not in (None, sid)): + if (state == 'stop' and jingle.sid not in (None, sid)): return
- setattr(self, jingle_type + '_state', jingle_state) - + new_sid = None if jingle_state == self.JINGLE_STATE_NULL: - setattr(self, jingle_type + '_sid', None) + new_sid = None if state in ('connection_received', 'connecting'): - setattr(self, jingle_type + '_sid', sid) + new_sid = sid + + jingle.state = jingle_state + jingle.sid = new_sid
var = GLib.Variant.new_boolean(jingle_state != self.JINGLE_STATE_NULL) - getattr(self, jingle_type + '_action').change_state(var) + jingle.action.change_state(var)
- getattr(self, 'update_' + jingle_type)() + jingle.update()
def set_audio_state(self, state, sid=None, reason=None): self._set_jingle_state('audio', state, sid=sid, reason=reason) @@ -739,7 +756,7 @@ class ChatControl(ChatControlBase): def _get_audio_content(self): con = app.connections[self.account] session = con.get_module('Jingle').get_jingle_session( - self.contact.get_full_jid(), self.audio_sid) + self.contact.get_full_jid(), self.jingle['audio'].sid) return session.get_content('audio')
def on_num_button_pressed(self, _widget, num): @@ -864,28 +881,31 @@ class ChatControl(ChatControlBase): self.xml.banner_name_label.set_markup(label_text) self.xml.banner_name_label.set_tooltip_text(label_tooltip)
- def close_jingle_content(self, jingle_type): - sid = getattr(self, jingle_type + '_sid') - if not sid: + def close_jingle_content(self, jingle_type: str) -> None: + jingle = self.jingle[jingle_type] + if not jingle.sid: return - setattr(self, jingle_type + '_sid', None) - setattr(self, jingle_type + '_state', self.JINGLE_STATE_NULL) + + jingle.sid = None + jingle.state = self.JINGLE_STATE_NULL + con = app.connections[self.account] session = con.get_module('Jingle').get_jingle_session( - self.contact.get_full_jid(), sid) + self.contact.get_full_jid(), jingle.sid) if session: content = session.get_content(jingle_type) if content: session.remove_content(content.creator, content.name) var = GLib.Variant.new_boolean(False) - getattr(self, jingle_type + '_action').change_state(var) - getattr(self, 'update_' + jingle_type)() + + jingle.action.change_state(var) + jingle.update()
def on_jingle_button_toggled(self, state, jingle_type): con = app.connections[self.account] if state: - if getattr(self, jingle_type + '_state') == \ - self.JINGLE_STATE_NULL: + if self.jingle[jingle_type].state == self.JINGLE_STATE_NULL: + con = app.connections[self.account] if jingle_type == 'video': video_hbox = self.xml.video_hbox video_hbox.set_no_show_all(False) @@ -893,26 +913,15 @@ class ChatControl(ChatControlBase): fixed = self.xml.outgoing_fixed fixed.set_no_show_all(False) video_hbox.show_all() - out_da = self.xml.outgoing_drawingarea - out_da.realize() - if os.name == 'nt': - out_xid = out_da.get_window().handle - else: - out_xid = out_da.get_window().get_xid() - else: - out_xid = None video_hbox.show_all() - in_da = self.xml.incoming_drawingarea - in_da.realize() - in_xid = in_da.get_window().get_xid() sid = con.get_module('Jingle').start_video( - self.contact.get_full_jid(), in_xid, out_xid) + self.contact.get_full_jid())
else: sid = con.get_module('Jingle').start_audio( self.contact.get_full_jid())
- getattr(self, 'set_' + jingle_type + '_state')('connecting', sid) + self.jingle[jingle_type].set_state('connecting', sid) else: video_hbox = self.xml.video_hbox video_hbox.set_no_show_all(True)
===================================== gajim/common/jingle_rtp.py ===================================== @@ -412,11 +412,8 @@ class JingleAudio(JingleRTPContent):
class JingleVideo(JingleRTPContent): - def __init__(self, session, transport=None, in_xid=0, out_xid=0): + def __init__(self, session, transport=None): JingleRTPContent.__init__(self, session, 'video', transport) - self.in_xid = in_xid - self.out_xid = out_xid - self.out_xid_set = False self.setup_stream()
def setup_stream(self): @@ -426,42 +423,24 @@ class JingleVideo(JingleRTPContent): JingleRTPContent.setup_stream(self, self._on_src_pad_added) bus = self.pipeline.get_bus() bus.enable_sync_message_emission() - bus.connect('sync-message::element', self._on_sync_message)
- # the local parts - if app.config.get('video_framerate'): - framerate = 'videorate ! video/x-raw,framerate=%s ! ' % \ - app.config.get('video_framerate') - else: - framerate = '' - try: - w, h = app.config.get('video_size').split('x') - except Exception: - w = h = None - if w and h: - video_size = 'video/x-raw,width=%s,height=%s ! ' % (w, h) - else: - video_size = '' + def do_setup(self, self_display_sink, other_sink): if app.config.get('video_see_self'): - tee = '! tee name=t ! queue ! videoscale ! ' + \ - 'video/x-raw,width=160,height=120 ! videoconvert ! ' + \ - '%s t. ! queue ' % app.config.get( - 'video_output_device') + tee = '! tee name=split ! queue name=self-display-queue split. ! queue name=network-queue' else: tee = ''
self.src_bin = self.make_bin_from_config('video_input_device', - '%%s %s! %svideoscale ! %svideoconvert' % - (tee, framerate, video_size), + '%%s %s' % tee, _("video input")) - self.pipeline.add(self.src_bin) + if app.config.get('video_see_self'): + self.pipeline.add(self_display_sink) + self_display_queue = self.src_bin.get_by_name('self-display-queue') + self_display_queue.get_static_pad('src').link_maybe_ghosting(self_display_sink.get_static_pad('sink')) self.pipeline.set_state(Gst.State.PLAYING)
- self.sink = self.make_bin_from_config('video_output_device', - 'videoscale ! videoconvert ! %s', - _("video output")) - + self.sink = other_sink self.pipeline.add(self.sink)
self.src_bin.get_static_pad('src').link(self.p2psession.get_property( @@ -469,28 +448,13 @@ class JingleVideo(JingleRTPContent):
# The following is needed for farstream to process ICE requests: self.pipeline.set_state(Gst.State.PLAYING) - - def _on_sync_message(self, bus, message): - if message.get_structure() is None: - return False - if message.get_structure().get_name() == 'prepare-window-handle': - message.src.set_property('force-aspect-ratio', True) - imagesink = message.src - if app.config.get('video_see_self') and not self.out_xid_set: - imagesink.set_window_handle(self.out_xid) - self.out_xid_set = True - else: - imagesink.set_window_handle(self.in_xid) + Gst.debug_bin_to_dot_file(self.pipeline, Gst.DebugGraphDetails.ALL, 'video-graph')
def get_fallback_src(self): # TODO: Use avatar? pipeline = 'videotestsrc is-live=true ! video/x-raw,framerate=10/1 ! videoconvert' return Gst.parse_bin_from_description(pipeline, True)
- def destroy(self): - JingleRTPContent.destroy(self) - self.pipeline.get_bus().disconnect_by_func(self._on_sync_message) - def get_content(desc): if desc['media'] == 'audio': return JingleAudio
===================================== gajim/common/modules/jingle.py ===================================== @@ -165,18 +165,18 @@ class Jingle(BaseModule): jingle.start_session() return jingle.sid
- def start_video(self, jid, in_xid, out_xid): + def start_video(self, jid): if self.get_jingle_session(jid, media='video'): return self.get_jingle_session(jid, media='video').sid jingle = self.get_jingle_session(jid, media='audio') if jingle: - jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid, - out_xid=out_xid)) + video = JingleVideo(jingle) + jingle.add_content('video', video) else: jingle = JingleSession(self._con, weinitiate=True, jid=jid) self._sessions[jingle.sid] = jingle - jingle.add_content('video', JingleVideo(jingle, in_xid=in_xid, - out_xid=out_xid)) + video = JingleVideo(jingle) + jingle.add_content('video', video) jingle.start_session() return jingle.sid
===================================== gajim/data/gui/chat_control.ui ===================================== @@ -942,16 +942,13 @@ microphone-sensitivity-high-symbolic</property> <property name="can_focus">False</property> <property name="no_show_all">True</property> <child> - <object class="GtkViewport" id="viewport1"> + <object class="GtkViewport" id="outgoing_viewport"> <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> - </object> + <placeholder/> </child> </object> </child> @@ -968,16 +965,11 @@ microphone-sensitivity-high-symbolic</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> <child> - <object class="GtkViewport" id="viewport2"> + <object class="GtkViewport" id="incoming_viewport"> <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> - </object> + <placeholder/> </child> </object> <packing>
===================================== gajim/dialogs.py ===================================== @@ -29,7 +29,6 @@ from typing import Dict # pylint: disable=unused-import from typing import List # pylint: disable=unused-import from typing import Tuple # pylint: disable=unused-import
-import os import uuid import logging
@@ -54,6 +53,7 @@ from gajim.gtk.util import get_icon_name from gajim.gtk.util import get_builder from gajim.gtk.util import get_activity_icon_name from gajim.gtk.util import get_app_window +from gajim.gtk import gstreamer
if app.is_installed('GSPELL'): from gi.repository import Gspell # pylint: disable=ungrouped-imports @@ -1100,34 +1100,12 @@ class VoIPCallReceivedDialog: fixed = ctrl.xml.get_object('outgoing_fixed') fixed.set_no_show_all(False) video_hbox.show_all() - ctrl.xml.get_object('incoming_drawingarea').realize() - if os.name == 'nt': - in_xid = ctrl.xml.get_object('incoming_drawingarea').\ - get_window().handle - else: - in_xid = ctrl.xml.get_object('incoming_drawingarea').\ - get_property('window').get_xid() content = session.get_content('video') - # move outgoing stream to chat window - if app.config.get('video_see_self'): - ctrl.xml.get_object('outgoing_drawingarea').realize() - if os.name == 'nt': - out_xid = ctrl.xml.get_object('outgoing_drawingarea').\ - get_window().handle - else: - out_xid = ctrl.xml.get_object('outgoing_drawingarea').\ - get_property('window').get_xid() - b = content.src_bin - for e in b.children: - if not e.get_name().startswith('autovideosink'): - continue - for f in e.children: - if f.get_name().startswith('autovideosink'): - f.set_window_handle(out_xid) - content.out_xid = out_xid - break - break - content.in_xid = in_xid + sink_other, widget_other, _ = gstreamer.create_gtk_widget() + sink_self, widget_self, _ = gstreamer.create_gtk_widget() + ctrl.xml.incoming_viewport.add(widget_other) + ctrl.xml.outgoing_viewport.add(widget_self) + content.do_setup(sink_self, sink_other) ctrl.set_video_state('connecting', self.sid) # Now, accept the content/sessions. # This should be done after the chat control is running
===================================== gajim/gtk/gstreamer.py ===================================== @@ -0,0 +1,24 @@ +from typing import Tuple, Optional + +from gi.repository import Gst +from gi.repository import Gtk + +def create_gtk_widget() -> Tuple[Optional[Gst.Element], Optional[Gtk.Widget], Optional[str]]: + gtkglsink = Gst.ElementFactory.make('gtkglsink', None) + if gtkglsink is not None: + glsinkbin = Gst.ElementFactory.make('glsinkbin', None) + if glsinkbin is None: + return None, None, None + glsinkbin.set_property('sink', gtkglsink) + sink = glsinkbin + widget = gtkglsink.get_property('widget') + name = 'gtkglsink' + else: + sink = Gst.ElementFactory.make('gtksink', None) + if sink is None: + return None, None, None + widget = sink.get_property('widget') + name = 'gtksink' + widget.set_visible(True) + widget.set_property('expand', True) + return sink, widget, name
===================================== gajim/gtk/preferences.py ===================================== @@ -38,6 +38,7 @@ from gajim.gtk.util import open_window from gajim.gtk.dialogs import AspellDictError from gajim.gtk.sounds import ManageSounds from gajim.gtk.const import ControlType +from gajim.gtk import gstreamer
try: from gajim.common.multimedia_helpers import AudioInputManager, AudioOutputManager @@ -384,7 +385,7 @@ class Preferences(Gtk.ApplicationWindow):
def on_av_map(tab): label = self._ui.selected_video_output - sink, widget, name = self.setup_video_output() + sink, widget, name = gstreamer.create_gtk_widget() if sink is None: log.error('Failed to obtain a working Gstreamer GTK+ sink, ' 'video support will be disabled') @@ -508,27 +509,6 @@ class Preferences(Gtk.ApplicationWindow): if event.keyval == Gdk.KEY_Escape: self.destroy()
- @staticmethod - def setup_video_output(): - gtkglsink = Gst.ElementFactory.make('gtkglsink', None) - if gtkglsink is not None: - glsinkbin = Gst.ElementFactory.make('glsinkbin', None) - if glsinkbin is None: - return None, None, None - glsinkbin.set_property('sink', gtkglsink) - sink = glsinkbin - widget = gtkglsink.get_property('widget') - name = 'gtkglsink' - else: - sink = Gst.ElementFactory.make('gtksink', None) - if sink is None: - return None, None, None - widget = sink.get_property('widget') - name = 'gtksink' - widget.set_visible(True) - widget.set_property('expand', True) - return sink, widget, name - def get_per_account_option(self, opt): """ Return the value of the option opt if it's the same in all accounts else
===================================== gajim/gui_interface.py ===================================== @@ -1024,7 +1024,7 @@ class Interface: account = obj.conn.name ctrl = (self.msg_win_mgr.get_control(obj.fjid, account) or self.msg_win_mgr.get_control(obj.jid, account)) - if ctrl and obj.sid == ctrl.audio_sid: + if ctrl and obj.sid == ctrl.jingle['audio'].sid: ctrl.set_audio_state('error', reason=obj.reason)
@staticmethod
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/7e6a3e30397c4f9cdf53da2c790786f2...