diff --git a/data/Makefile.am b/data/Makefile.am
index f555d9a84..ece792553 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -15,6 +15,7 @@ desktop_DATA = gnome-shell.desktop gnome-shell-extension-prefs.desktop
introspectiondir = $(datadir)/dbus-1/interfaces
introspection_DATA = \
+ org.gnome.Shell.Screencast.xml \
org.gnome.Shell.Screenshot.xml \
org.gnome.ShellSearchProvider.xml \
org.gnome.ShellSearchProvider2.xml
diff --git a/data/org.gnome.Shell.Screencast.xml b/data/org.gnome.Shell.Screencast.xml
new file mode 100644
index 000000000..6be4010dc
--- /dev/null
+++ b/data/org.gnome.Shell.Screencast.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/js/Makefile.am b/js/Makefile.am
index 62c8323ad..e8dd927dc 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -78,6 +78,7 @@ nobase_dist_js_DATA = \
ui/popupMenu.js \
ui/remoteSearch.js \
ui/runDialog.js \
+ ui/screencast.js \
ui/screenshot.js \
ui/screenShield.js \
ui/scripting.js \
diff --git a/js/ui/screencast.js b/js/ui/screencast.js
new file mode 100644
index 000000000..642d40ac6
--- /dev/null
+++ b/js/ui/screencast.js
@@ -0,0 +1,138 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+const Lang = imports.lang;
+const Shell = imports.gi.Shell;
+
+const Hash = imports.misc.hash;
+const Main = imports.ui.main;
+
+const ScreencastIface =
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+;
+
+const ScreencastService = new Lang.Class({
+ Name: 'ScreencastService',
+
+ _init: function() {
+ this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreencastIface, this);
+ this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screencast');
+
+ Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null);
+
+ this._recorders = new Hash.Map();
+
+ Main.sessionMode.connect('updated',
+ Lang.bind(this, this._sessionModeChanged));
+ },
+
+ _ensureRecorderForSender: function(sender) {
+ let recorder = this._recorders.get(sender);
+ if (!recorder) {
+ recorder = new Shell.Recorder({ stage: global.stage });
+ recorder._watchNameId =
+ Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
+ Lang.bind(this, this._onNameVanished));
+ this._recorders.set(sender, recorder);
+ }
+ return recorder;
+ },
+
+ _sessionModeChanged: function() {
+ if (Main.sessionMode.allowScreencast)
+ return;
+
+ for (let sender in this._recorders.keys())
+ this._recorders.delete(sender);
+ },
+
+ _onNameVanished: function(connection, name) {
+ this._stopRecordingForSender(name);
+ },
+
+ _stopRecordingForSender: function(sender) {
+ let recorder = this._recorders.get(sender);
+ if (!recorder)
+ return false;
+
+ Gio.bus_unwatch_name(recorder._watchNameId);
+ recorder.close();
+ this._recorders.delete(sender);
+
+ return true;
+ },
+
+ _applyOptionalParameters: function(recorder, options) {
+ for (let option in options)
+ options[option] = options[option].deep_unpack();
+
+ if (options['pipeline'])
+ recorder.set_pipeline(options['pipeline']);
+ if (options['framerate'])
+ recorder.set_framerate(options['framerate']);
+ if (options['draw-cursor'])
+ recorder.set_draw_cursor(options['draw-cursor']);
+ },
+
+ ScreencastAsync: function(params, invocation) {
+ let returnValue = [false, ''];
+ if (!Main.sessionMode.allowScreencast)
+ invocation.return_value(GLib.Variant.new('(bs)', returnValue));
+
+ let sender = invocation.get_sender();
+ let recorder = this._ensureRecorderForSender(sender);
+ if (!recorder.is_recording()) {
+ let [fileTemplate, options] = params;
+
+ recorder.set_file_template(fileTemplate);
+ this._applyOptionalParameters(recorder, options);
+ returnValue = recorder.record();
+ }
+
+ invocation.return_value(GLib.Variant.new('(bs)', returnValue));
+ },
+
+ ScreencastAreaAsync: function(params, invocation) {
+ let returnValue = [false, ''];
+ if (!Main.sessionMode.allowScreencast)
+ invocation.return_value(GLib.Variant.new('(bs)', returnValue));
+
+ let sender = invocation.get_sender();
+ let recorder = this._ensureRecorderForSender(sender);
+
+ if (!recorder.is_recording()) {
+ let [x, y, width, height, fileTemplate, options] = params;
+
+ recorder.set_file_template(fileTemplate);
+ recorder.set_area(x, y, width, height);
+ this._applyOptionalParameters(recorder, options);
+ returnValue = recorder.record();
+ }
+
+ invocation.return_value(GLib.Variant.new('(bs)', returnValue));
+ },
+
+ StopScreencastAsync: function(params, invocation) {
+ let success = this._stopRecordingForSender(invocation.get_sender());
+ invocation.return_value(GLib.Variant.new('(b)', [success]));
+ }
+});
diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js
index 12e4b844b..952613b31 100644
--- a/js/ui/shellDBus.js
+++ b/js/ui/shellDBus.js
@@ -12,6 +12,7 @@ const ExtensionDownloader = imports.ui.extensionDownloader;
const ExtensionUtils = imports.misc.extensionUtils;
const Hash = imports.misc.hash;
const Main = imports.ui.main;
+const Screencast = imports.ui.screencast;
const Screenshot = imports.ui.screenshot;
const GnomeShellIface =
@@ -70,6 +71,7 @@ const GnomeShell = new Lang.Class({
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
this._extensionsService = new GnomeShellExtensions();
+ this._screencastService = new Screencast.ScreencastService();
this._screenshotService = new Screenshot.ScreenshotService();
this._grabbedAccelerators = new Hash.Map();