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.
-->
<interface name="org.gnome.Shell.Screencast">
<property name="ScreencastSupported" type="b" access="read"/>
<!--
Screencast:

View File

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

View File

@ -258,9 +258,17 @@ var Recorder = class {
};
var ScreencastService = class extends ServiceImplementation {
static canScreencast() {
return Gst.init_check(null) &&
Gst.ElementFactory.find('pipewiresrc') &&
Gst.ElementFactory.find('filesink');
}
constructor() {
super(ScreencastIface, '/org/gnome/Shell/Screencast');
this._canScreencast = ScreencastService.canScreencast();
Gst.init(null);
Gtk.init();
@ -280,6 +288,10 @@ var ScreencastService = class extends ServiceImplementation {
'/org/gnome/Shell/Introspect');
}
get ScreencastSupported() {
return this._canScreencast;
}
_removeRecorder(sender) {
this._recorders.delete(sender);
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',
'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 Config = imports.misc.config;
const GrabHelper = imports.ui.grabHelper;
const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox;
@ -1019,6 +1018,19 @@ var ScreenshotUI = GObject.registerClass({
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' });
// 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',
icon_name: 'camera-web-symbolic',
toggle_mode: true,
visible: Config.HAVE_RECORDER,
visible: false,
});
this._castButton.connect('notify::checked',
this._onCastButtonToggled.bind(this));
@ -1438,7 +1450,7 @@ var ScreenshotUI = GObject.registerClass({
if (this._screencastInProgress)
return;
if (mode === UIMode.SCREENCAST && !Config.HAVE_RECORDER)
if (mode === UIMode.SCREENCAST && !this._screencastProxy.ScreencastSupported)
return;
this._castButton.checked = mode === UIMode.SCREENCAST;
@ -1809,7 +1821,7 @@ var ScreenshotUI = GObject.registerClass({
}
}
_startScreencast() {
async _startScreencast() {
if (this._windowButton.checked)
return; // TODO
@ -1833,61 +1845,40 @@ var ScreenshotUI = GObject.registerClass({
this.close(true);
// This is a bit awkward because creating a proxy synchronously hangs Shell.
const doStartScreencast = async () => {
let method =
this._screencastProxy.ScreencastAsync.bind(this._screencastProxy);
if (w !== -1) {
method = this._screencastProxy.ScreencastAreaAsync.bind(
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');
}
};
let method =
this._screencastProxy.ScreencastAsync.bind(this._screencastProxy);
if (w !== -1) {
method = this._screencastProxy.ScreencastAreaAsync.bind(
this._screencastProxy, x, y, w, h);
}
// Set this before calling the method as the screen recording indicator
// will check it before the success callback fires.
this._setScreencastInProgress(true);
if (this._screencastProxy) {
doStartScreencast();
} else {
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._screencastProxy = object;
doStartScreencast();
}
);
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');
}
}