Commit 220f46b4 authored by Emmanuel Gil Peyrot's avatar Emmanuel Gil Peyrot

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 7207b824
Pipeline #4042 passed with stages
in 3 minutes and 7 seconds
......@@ -266,7 +266,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'],
'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
......@@ -40,7 +41,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
......@@ -48,6 +50,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):
......@@ -332,7 +336,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)
......@@ -347,18 +352,15 @@ 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',
......@@ -371,9 +373,66 @@ class Preferences(Gtk.ApplicationWindow):
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')
label.set_markup(_('<span color="red" font-weight="bold">Unavailable</span>, video support will be disabled'))
{'gtkglsink': _('<span color="green" font-weight="bold">OpenGL</span> accelerated'),
'gtksink': _('<span color="yellow" font-weight="bold">Unaccelerated</span>')}[name])
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')
......@@ -437,6 +496,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
......@@ -920,6 +1000,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')
......@@ -928,10 +1009,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