screenshot: Move HAVE_RECORDER check into screencast service

Some gstreamer plugins require a connection to the display server,
so if we end up initializing gstreamer before we are ourselves
fully initialized, we may end up with a locked compositor.

Avoid this by moving the runtime recorder check into the screencast
D-Bus service, so that all gstreamer calls happen out of process.

https://gitlab.gnome.org/GNOME/gnome-shell/-/issues/5710

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2519>
This commit is contained in:
Florian Müllner 2022-10-19 12:43:29 +02:00
parent cff56d4b03
commit f266c2ca15
5 changed files with 61 additions and 63 deletions

View File

@ -10,6 +10,7 @@
The interface used to record screen contents. The interface used to record screen contents.
--> -->
<interface name="org.gnome.Shell.Screencast"> <interface name="org.gnome.Shell.Screencast">
<property name="ScreencastSupported" type="b" access="read"/>
<!-- <!--
Screencast: Screencast:

View File

@ -1,13 +1,12 @@
/* exported main */ /* exported main */
const Config = imports.misc.config; const {DBusService} = imports.dbusService;
const { DBusService } = imports.dbusService;
function main() { function main() {
if (!Config.HAVE_RECORDER) const {ScreencastService} = imports.screencastService;
if (!ScreencastService.canScreencast())
return; return;
const { ScreencastService } = imports.screencastService;
const service = new DBusService( const service = new DBusService(
'org.gnome.Shell.Screencast', 'org.gnome.Shell.Screencast',
new ScreencastService()); new ScreencastService());

View File

@ -258,9 +258,17 @@ var Recorder = class {
}; };
var ScreencastService = class extends ServiceImplementation { var ScreencastService = class extends ServiceImplementation {
static canScreencast() {
return Gst.init_check(null) &&
Gst.ElementFactory.find('pipewiresrc') &&
Gst.ElementFactory.find('filesink');
}
constructor() { constructor() {
super(ScreencastIface, '/org/gnome/Shell/Screencast'); super(ScreencastIface, '/org/gnome/Shell/Screencast');
this._canScreencast = ScreencastService.canScreencast();
Gst.init(null); Gst.init(null);
Gtk.init(); Gtk.init();
@ -280,6 +288,10 @@ var ScreencastService = class extends ServiceImplementation {
'/org/gnome/Shell/Introspect'); '/org/gnome/Shell/Introspect');
} }
get ScreencastSupported() {
return this._canScreencast;
}
_removeRecorder(sender) { _removeRecorder(sender) {
this._recorders.delete(sender); this._recorders.delete(sender);
if (this._recorders.size === 0) if (this._recorders.size === 0)

View File

@ -21,8 +21,3 @@ var LIBMUTTER_API_VERSION = '@LIBMUTTER_API_VERSION@'
var HAVE_BLUETOOTH = pkg.checkSymbol('GnomeBluetooth', '3.0', var HAVE_BLUETOOTH = pkg.checkSymbol('GnomeBluetooth', '3.0',
'Client.default_adapter_state') 'Client.default_adapter_state')
var HAVE_RECORDER =
pkg.checkSymbol('Gst', '1.0') &&
imports.gi.Gst.init_check(null) &&
imports.gi.Gst.ElementFactory.find('pipewiresrc') &&
imports.gi.Gst.ElementFactory.find('filesink');

View File

@ -3,7 +3,6 @@
const { Clutter, Cogl, Gio, GObject, GLib, Graphene, Gtk, Meta, Shell, St } = imports.gi; const { Clutter, Cogl, Gio, GObject, GLib, Graphene, Gtk, Meta, Shell, St } = imports.gi;
const Config = imports.misc.config;
const GrabHelper = imports.ui.grabHelper; const GrabHelper = imports.ui.grabHelper;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
@ -1019,6 +1018,19 @@ var ScreenshotUI = GObject.registerClass({
this._screencastInProgress = false; this._screencastInProgress = false;
this._screencastProxy = new ScreencastProxy(
Gio.DBus.session,
'org.gnome.Shell.Screencast',
'/org/gnome/Shell/Screencast',
(object, error) => {
if (error !== null) {
log('Error connecting to the screencast service');
return;
}
this._castButton.visible = this._screencastProxy.ScreencastSupported;
});
this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' }); this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });
// The full-screen screenshot has a separate container so that we can // The full-screen screenshot has a separate container so that we can
@ -1212,7 +1224,7 @@ var ScreenshotUI = GObject.registerClass({
style_class: 'screenshot-ui-shot-cast-button', style_class: 'screenshot-ui-shot-cast-button',
icon_name: 'camera-web-symbolic', icon_name: 'camera-web-symbolic',
toggle_mode: true, toggle_mode: true,
visible: Config.HAVE_RECORDER, visible: false,
}); });
this._castButton.connect('notify::checked', this._castButton.connect('notify::checked',
this._onCastButtonToggled.bind(this)); this._onCastButtonToggled.bind(this));
@ -1438,7 +1450,7 @@ var ScreenshotUI = GObject.registerClass({
if (this._screencastInProgress) if (this._screencastInProgress)
return; return;
if (mode === UIMode.SCREENCAST && !Config.HAVE_RECORDER) if (mode === UIMode.SCREENCAST && !this._screencastProxy.ScreencastSupported)
return; return;
this._castButton.checked = mode === UIMode.SCREENCAST; this._castButton.checked = mode === UIMode.SCREENCAST;
@ -1809,7 +1821,7 @@ var ScreenshotUI = GObject.registerClass({
} }
} }
_startScreencast() { async _startScreencast() {
if (this._windowButton.checked) if (this._windowButton.checked)
return; // TODO return; // TODO
@ -1833,61 +1845,40 @@ var ScreenshotUI = GObject.registerClass({
this.close(true); this.close(true);
// This is a bit awkward because creating a proxy synchronously hangs Shell. // This is a bit awkward because creating a proxy synchronously hangs Shell.
const doStartScreencast = async () => { let method =
let method = this._screencastProxy.ScreencastAsync.bind(this._screencastProxy);
this._screencastProxy.ScreencastAsync.bind(this._screencastProxy); if (w !== -1) {
if (w !== -1) { method = this._screencastProxy.ScreencastAreaAsync.bind(
method = this._screencastProxy.ScreencastAreaAsync.bind( this._screencastProxy, x, y, w, h);
this._screencastProxy, x, y, w, h); }
}
try {
const [success, path] = await method(
GLib.build_filenamev([
/* Translators: this is the folder where recorded
screencasts are stored. */
_('Screencasts'),
/* Translators: this is a filename used for screencast
* recording, where "%d" and "%t" date and time, e.g.
* "Screencast from 07-17-2013 10:00:46 PM.webm" */
/* xgettext:no-c-format */
_('Screencast from %d %t.webm'),
]),
{'draw-cursor': new GLib.Variant('b', drawCursor)});
if (!success)
throw new Error();
this._screencastPath = path;
} catch (error) {
this._setScreencastInProgress(false);
const {message} = error;
if (message)
log(`Error starting screencast: ${message}`);
else
log('Error starting screencast');
}
};
// Set this before calling the method as the screen recording indicator // Set this before calling the method as the screen recording indicator
// will check it before the success callback fires. // will check it before the success callback fires.
this._setScreencastInProgress(true); this._setScreencastInProgress(true);
if (this._screencastProxy) { try {
doStartScreencast(); const [success, path] = await method(
} else { GLib.build_filenamev([
new ScreencastProxy( /* Translators: this is the folder where recorded
Gio.DBus.session, screencasts are stored. */
'org.gnome.Shell.Screencast', _('Screencasts'),
'/org/gnome/Shell/Screencast', /* Translators: this is a filename used for screencast
(object, error) => { * recording, where "%d" and "%t" date and time, e.g.
if (error !== null) { * "Screencast from 07-17-2013 10:00:46 PM.webm" */
log('Error connecting to the screencast service'); /* xgettext:no-c-format */
return; _('Screencast from %d %t.webm'),
} ]),
{'draw-cursor': new GLib.Variant('b', drawCursor)});
this._screencastProxy = object; if (!success)
doStartScreencast(); throw new Error();
} this._screencastPath = path;
); } catch (error) {
this._setScreencastInProgress(false);
const {message} = error;
if (message)
log(`Error starting screencast: ${message}`);
else
log('Error starting screencast');
} }
} }