-
-
Notifications
You must be signed in to change notification settings - Fork 320
/
gnome-shell-dbus-recorder.vala
214 lines (180 loc) · 7.61 KB
/
gnome-shell-dbus-recorder.vala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
/*
This file is part of Peek.
Copyright (c) 2017-2018, 2021 by Philipp Wolfer <ph.wolfer@gmail.com>
Copyright (c) 2021 Andreas Dangel <andreas.dangel@adangel.org>
This software is licensed under the GNU General Public License
(version 3 or later). See the LICENSE file in this distribution.
*/
#if ! DISABLE_GNOME_SHELL
using Gnome;
using Gnome.ShellNS;
using Peek.PostProcessing;
namespace Peek.Recording {
public class GnomeShellDbusRecorder : BaseScreenRecorder {
private Screencast screencast;
private const string DBUS_NAME = "org.gnome.Shell.Screencast";
private uint wait_timeout = 0;
public GnomeShellDbusRecorder () throws IOError {
base ();
screencast = Bus.get_proxy_sync (
BusType.SESSION,
DBUS_NAME,
"/org/gnome/Shell/Screencast");
}
~GnomeShellDbusRecorder () {
if (wait_timeout != 0) {
Source.remove (wait_timeout);
}
}
protected override void start_recording (RecordingArea area) throws RecordingError {
bool success = false;
var options = new HashTable<string, Variant> (null, null);
options.insert ("framerate", new Variant.int32 (config.framerate));
options.insert ("pipeline", build_gst_pipeline (area));
if (!config.capture_mouse) {
options.insert ("draw-cursor", false);
}
try {
string file_template = Utils.create_temp_file (
get_temp_file_extension ()
);
int width = area.width;
int height = area.height;
screencast.screencast_area (
area.left, area.top, width, height,
file_template, options, out success, out temp_file);
if (success) {
stdout.printf ("Recording to file %s\n", temp_file);
} else {
var message = new StringBuilder ();
message.append("Could not start GNOME Shell recorder.\n\n");
message.append("Missing codec or another active screen recording using org.gnome.Shell.Screencast?");
message.append("\n\nPlease see the FAQ at https://github.com/phw/peek#what-is-the-cause-for-could-not-start-gnome-shell-recorder-errors");
throw new RecordingError.INITIALIZING_RECORDING_FAILED (message.str);
}
} catch (DBusError e) {
throw new RecordingError.INITIALIZING_RECORDING_FAILED (e.message);
} catch (IOError e) {
throw new RecordingError.INITIALIZING_RECORDING_FAILED (e.message);
} catch (FileError e) {
throw new RecordingError.INITIALIZING_RECORDING_FAILED (e.message);
}
is_recording = success;
}
public static bool is_available () throws PeekError {
// In theory the dbus service can be installed, but it will only work
// if GNOME Shell is running.
if (!DesktopIntegration.is_gnome_shell ()) {
return false;
}
try {
Freedesktop.DBus dbus = Bus.get_proxy_sync (
BusType.SESSION,
"org.freedesktop.DBus",
"/org/freedesktop/DBus");
try {
// The service might need to get started before being available
dbus.start_service_by_name (DBUS_NAME, 0);
} catch (DBusError e) {
return false;
}
return dbus.name_has_owner (DBUS_NAME);
} catch (DBusError e) {
stderr.printf ("Error: %s\n", e.message);
throw new PeekError.SCREEN_RECORDER_ERROR (e.message);
} catch (IOError e) {
stderr.printf ("Error: %s\n", e.message);
throw new PeekError.SCREEN_RECORDER_ERROR (e.message);
}
}
private static bool is_gnome_40_or_higher () throws RecordingError {
if (!DesktopIntegration.is_gnome ()) {
return false;
}
try {
Gnome.Shell gnomeShell = Bus.get_proxy_sync (
BusType.SESSION,
"org.gnome.Shell",
"/org/gnome/Shell");
return int.parse (gnomeShell.shell_version.split ( "." )[0] ) >= 40;
} catch (IOError e) {
stderr.printf ("Error: %s\n", e.message);
throw new RecordingError.INITIALIZING_RECORDING_FAILED (e.message);
}
}
protected override void stop_recording () {
try {
screencast.stop_screencast ();
if (!is_cancelling) {
// Add a small timeout after GNOME Shell recorder was stopped.
// The recorder will stop the GST pipeline, but there might be still
// some cleanup / finalization to do. Without this the post-processing
// sometimes fails.
wait_timeout = Timeout.add_full (GLib.Priority.LOW, 400, () => {
Source.remove (wait_timeout);
wait_timeout = 0;
finalize_recording ();
return true;
});
}
} catch (DBusError e) {
stderr.printf ("Error: %s\n", e.message);
if (!is_cancelling) {
recording_aborted (new RecordingError.RECORDING_ABORTED (e.message));
}
} catch (IOError e) {
stderr.printf ("Error: %s\n", e.message);
if (!is_cancelling) {
recording_aborted (new RecordingError.RECORDING_ABORTED (e.message));
}
}
}
private string build_gst_pipeline (RecordingArea area) throws RecordingError {
// Default pipeline is for GNOME Shell up to 2.22:
// "vp8enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmux"
// GNOME Shell 3.24 will use vp9enc with same settings.
// See https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/src/shell-recorder.c#L149
// Gnome Shell 40:
// https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/main/js/dbusServices/screencast/screencastService.js#L26
// https://gitlab.gnome.org/GNOME/gnome-shell/-/commit/51bf7ec17617a9ed056dd563afdb98e17da07373
var pipeline = new StringBuilder ();
if (is_gnome_40_or_higher ()) {
pipeline.append ("videoconvert chroma-mode=GST_VIDEO_CHROMA_MODE_NONE dither=GST_VIDEO_DITHER_NONE matrix-mode=GST_VIDEO_MATRIX_MODE_OUTPUT_ONLY n-threads=%T ! queue ! ");
}
if (config.downsample > 1) {
int width = area.width / config.downsample;
int height = area.height / config.downsample;
pipeline.append_printf (
"videoscale ! video/x-raw,width=%i,height=%i ! ", width, height);
}
if (config.output_format == OutputFormat.WEBM) {
pipeline.append ("vp8enc cpu-used=16 max-quantizer=17 deadline=1 keyframe-mode=disabled threads=%T static-threshold=1000 buffer-size=20000 ! ");
if (config.capture_sound) {
pipeline.append ("queue ! mux. pulsesrc ! queue ! audioconvert ! vorbisenc ! ");
}
pipeline.append ("queue ! mux. webmmux name=mux");
} else {
// We could use lossless x264 here, but x264enc is part of
// gstreamer1.0-plugins-ugly and not always available.
// Being near lossless here is important to avoid color distortions and
// dirty frames in the final GIF.
pipeline.append ("vp8enc cpu-used=16 min-quantizer=0 max-quantizer=0 deadline=1 keyframe-mode=disabled threads=%T static-threshold=1000 buffer-size=20000 ! ");
pipeline.append ("queue ! webmmux");
}
debug ("Using GStreamer pipeline %s", pipeline.str);
debug ("Debug with gst-launch-1.0 --gst-debug=3 ximagesrc %s ! filesink location=screencast", pipeline.str);
return pipeline.str;
}
private string get_temp_file_extension () {
string extension;
if (config.output_format == OutputFormat.GIF
|| config.output_format == OutputFormat.APNG) {
extension = "webm";
} else {
extension = Utils.get_file_extension_for_format (config.output_format);
}
return extension;
}
}
}
#endif