Commit 97625a72 authored by Link Mauve's avatar Link Mauve Committed by Philipp Hörist

Preferences: Add a video preview widget

In order to work on every system Gajim runs on (instead of only X11),
this new widget uses GTK+ Gstreamer elements.

It currently completely breaks the actual Jingle thing, but that was
already pretty broken so will be fixed in a later commit.

It might be nice to think about the way we want to make preferences
interact with a currently-running audio/video session, since it
currently uses a separate pipeline and thus will conflict when e.g. both
will want to access the same camera device, if the OS doesn’t implement
multiplexing (Linux doesn’t).
parent f70365da
Pipeline #4323 passed with stages
in 2 minutes and 46 seconds
......@@ -257,7 +257,6 @@ class Config:
'audio_input_device': [opt_str, 'autoaudiosrc ! volume name=gajim_vol'],
'audio_output_device': [opt_str, 'autoaudiosink'],
'video_input_device': [opt_str, 'autovideosrc'],
'video_output_device': [opt_str, 'autovideosink'],
  • Probably this is still WIP but removing 'video_output_device' config from everywhere but leaving it in and breaks the Video Chat with an error:

    Traceback (most recent call last):
      File "/usr/lib64/python3.6/site-packages/gajim/common/", line 116, in make_bin_from_config
        gst_bin = Gst.parse_bin_from_description(pipeline, True)
    gi.repository.GLib.Error: gst_parse_error: no element "None" (1)
    During handling of the above exception, another exception occurred:
    Traceback (most recent call last):
      File "/usr/lib64/python3.6/site-packages/gajim/", line 385, in _on_video
        self.on_jingle_button_toggled(state, 'video')
      File "/usr/lib64/python3.6/site-packages/gajim/", line 906, in on_jingle_button_toggled, in_xid, out_xid)
      File "/usr/lib64/python3.6/site-packages/gajim/common/modules/", line 179, in start_video
      File "/usr/lib64/python3.6/site-packages/gajim/common/", line 420, in __init__
      File "/usr/lib64/python3.6/site-packages/gajim/common/", line 463, in setup_stream
        _("video output"))
      File "/usr/lib64/python3.6/site-packages/gajim/common/", line 127, in make_bin_from_config
        raise JingleContentSetupException

    Would there be a fix soon?

  • There already is a fix, in !551, but it hasn’t been merged yet.

    Note that it doesn’t allow video chat to go through yet.

    Edited by Link Mauve
Please register or sign in to reply
'video_framerate': [opt_str, '', _('Optionally fix Jingle output video framerate. Example: 10/1 or 25/2.')],
'video_size': [opt_str, '', _('Optionally resize Jingle output video. Example: 320x240.')],
'video_see_self': [opt_bool, True, _('If enabled, you will see your webcam\'s video stream as well.')],
......@@ -97,24 +97,16 @@ class VideoInputManager(DeviceManager):
self.devices = {}
# Test src
self.detect_element('videotestsrc', _('Video test'),
'%s is-live=true ! video/x-raw,framerate=10/1')
'%s is-live=true ! video/x-raw,framerate=10/1 ! videoconvert')
# Auto src
self.detect_element('autovideosrc', _('Autodetect'))
# V4L2 src
# Best source on Linux, for both camera and screen sharing
self.detect_element('pipewiresrc', _('Pipewire'))
# Camera source on Linux
self.detect_element('v4l2src', _('V4L2: %s'))
# Funny things, just to test...
# self.devices['GOOM'] = 'audiotestsrc ! goom'
self.detect_element('ximagesrc', _('Screen'), '%s ! ffmpegcolorspace')
class VideoOutputManager(DeviceManager):
def detect(self):
self.devices = {}
# Fake video output
self.detect_element('fakesink', _('Fake video output'))
# Auto sink
_('X Window System (X11/XShm/Xv): %s'))
# ximagesink
self.detect_element('ximagesink', _('X Window System (without Xv)'))
self.detect_element('autovideosink', _('Autodetect'))
# X11 screen sharing on Linux
self.detect_element('ximagesrc', _('X11'), '%s ! ffmpegcolorspace')
# Recommended source on Windows
self.detect_element('ksvideosrc', _('Windows'))
# Recommended source on OS X
self.detect_element('avfvideosrc', _('macOS'))
......@@ -2232,9 +2232,8 @@ $T will be replaced by auto-not-available timeout.</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">V_ideo output device</property>
<property name="use_underline">True</property>
<property name="mnemonic_widget">video_output_combobox</property>
<property name="label" translatable="yes">Video output</property>
<property name="mnemonic_widget">selected_video_output</property>
<class name="dim-label"/>
......@@ -2245,10 +2244,10 @@ $T will be replaced by auto-not-available timeout.</property>
<object class="GtkComboBox" id="video_output_combobox">
<object class="GtkLabel" id="selected_video_output">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="changed" handler="on_video_output_combobox_changed" swapped="no"/>
<property name="halign">start</property>
<property name="left_attach">1</property>
......@@ -13,6 +13,7 @@
# along with Gajim. If not, see <>.
import os
import logging
from gi.repository import Gtk
from gi.repository import Gdk
......@@ -41,7 +42,8 @@ from gajim.gtk.sounds import ManageSounds
from gajim.common.multimedia_helpers import AudioInputManager, AudioOutputManager
from gajim.common.multimedia_helpers import VideoInputManager, VideoOutputManager
from gajim.common.multimedia_helpers import VideoInputManager
from gi.repository import Gst # pylint: disable=ungrouped-imports
HAS_GST = True
except (ImportError, ValueError):
HAS_GST = False
......@@ -49,6 +51,8 @@ except (ImportError, ValueError):
if app.is_installed('GSPELL'):
from gi.repository import Gspell # pylint: disable=ungrouped-imports
log = logging.getLogger('gajim.gtk.preferences')
class Preferences(Gtk.ApplicationWindow):
def __init__(self):
......@@ -333,7 +337,8 @@ class Preferences(Gtk.ApplicationWindow):
### Audio/Video tab ###
def create_av_combobox(opt_name, device_dict, config_name=None,
# This key is there to give the first index to autovideosrc and co.
key=lambda x: '' if x[1].startswith('auto') else x[0].lower()):
combobox = self._ui.get_object(opt_name + '_combobox')
cell = Gtk.CellRendererText()
cell.set_property('ellipsize', Pango.EllipsizeMode.END)
......@@ -348,33 +353,109 @@ class Preferences(Gtk.ApplicationWindow):
config = app.config.get(opt_name + '_device')
for index, (name, value) in enumerate(sorted(device_dict.items(),
model.append((name, value))
if config == value:
if HAS_GST and app.is_installed('FARSTREAM'):
create_av_combobox('audio_input', AudioInputManager().get_devices())
create_av_combobox('audio_output', AudioOutputManager().get_devices(
create_av_combobox('audio_output', AudioOutputManager().get_devices())
create_av_combobox('video_input', VideoInputManager().get_devices())
create_av_combobox('video_output', VideoOutputManager().get_devices(
create_av_combobox('video_framerate', {_('Default'): '',
'15fps': '15/1', '10fps': '10/1', '5fps': '5/1',
'2.5fps': '5/2'}, 'video_framerate', key=lambda x: -1 if \
not x[1] else float(x[0][:-3]))
create_av_combobox('video_size', {_('Default'): '',
'800x600': '800x600', '640x480': '640x480',
'320x240': '320x240'}, 'video_size', key=lambda x: -1 if \
not x[1] else int(x[0][:3]))
{_('Default'): '',
'15fps': '15/1',
'10fps': '10/1',
'5fps': '5/1',
'2.5fps': '5/2'},
key=lambda x: -1 if not x[1] else float(x[0][:-3]))
{_('Default'): '',
'800x600': '800x600',
'640x480': '640x480',
'320x240': '320x240'},
key=lambda x: -1 if not x[1] else int(x[0][:3]))
st = app.config.get('video_see_self')
def on_av_map(tab):
label = self._ui.selected_video_output
sink, widget, name = self.setup_video_output()
if sink is None:
log.error('Failed to obtain a working Gstreamer GTK+ sink, '
'video support will be disabled')
_('<span color="red" font-weight="bold">'
'Unavailable</span>, video support will be disabled'))
text = ''
if name == 'gtkglsink':
text = _('<span color="green" font-weight="bold">'
'OpenGL</span> accelerated')
elif name == 'gtksink':
text = _('<span color="yellow" font-weight="bold">'
if self.av_pipeline is None:
self.av_pipeline ='preferences-pipeline')
self.av_sink = sink
if self.av_widget is not None:
self.av_widget = widget
src_name = app.config.get('video_input_device')
self.av_src = Gst.parse_bin_from_description(src_name, True)
if self.av_src is not None:
# Parsing the pipeline stored in video_input_device failed,
# let’s try the default one.
self.av_src = Gst.ElementFactory.make('autovideosrc', None)
if self.av_src is None:
log.error('Failed to obtain a working Gstreamer source,'
' video will be disabled.')
# Great, this succeeded, let’s store it back into the
# config and use it. We’ve made autovideosrc the first
# element in the combobox so we can pick index 0 without
# worry.
combobox = self._ui.video_input_combobox
def on_av_unmap(tab):
self.av_src = None
self.av_sink = None
self.av_widget = None
self.av_pipeline = None
self.av_src = None
self.av_sink = None
self.av_widget = None
tab = self._ui.audio_video_tab
tab.connect('map', on_av_map)
tab.connect('unmap', on_av_unmap)
for opt_name in ('audio_input', 'audio_output', 'video_input',
'video_output', 'video_framerate', 'video_size'):
'video_framerate', 'video_size'):
combobox = self._ui.get_object(opt_name + '_combobox')
......@@ -438,6 +519,27 @@ class Preferences(Gtk.ApplicationWindow):
if event.keyval == Gdk.KEY_Escape:
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'
sink = Gst.ElementFactory.make('gtksink', None)
if sink is None:
return None, None, None
widget = sink.get_property('widget')
name = 'gtksink'
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
......@@ -919,6 +1021,7 @@ class Preferences(Gtk.ApplicationWindow):
active = combobox.get_active()
device = model[active][1]
app.config.set(config_name, device)
return device
def on_audio_input_combobox_changed(self, widget):
self.on_av_combobox_changed(widget, 'audio_input_device')
......@@ -927,10 +1030,19 @@ class Preferences(Gtk.ApplicationWindow):
self.on_av_combobox_changed(widget, 'audio_output_device')
def on_video_input_combobox_changed(self, widget):
self.on_av_combobox_changed(widget, 'video_input_device')
device = self.on_av_combobox_changed(widget, 'video_input_device')
src = Gst.parse_bin_from_description(device, True)
if src is None:
def on_video_output_combobox_changed(self, widget):
self.on_av_combobox_changed(widget, 'video_output_device')
if self.av_src is not None:
self.av_src = src
def on_video_framerate_combobox_changed(self, widget):
self.on_av_combobox_changed(widget, 'video_framerate')
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment