Add a screencast indicator for when we're recording
This will replace the indicator painted on the stage right now. This unfortunately does not work for the recorder triggered by the keybinding -- we'll simply replace the in-shell code with a keybinding powered by gnome-settings-daemon.
This commit is contained in:
parent
81bb7009ea
commit
d4942858ba
@ -660,6 +660,10 @@ StScrollBar StButton#vhandle:active {
|
|||||||
icon-size: 32px;
|
icon-size: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.screencast-indicator {
|
||||||
|
color: #ff0000;
|
||||||
|
}
|
||||||
|
|
||||||
/* Overview */
|
/* Overview */
|
||||||
|
|
||||||
#overview {
|
#overview {
|
||||||
|
@ -96,6 +96,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/status/rfkill.js \
|
ui/status/rfkill.js \
|
||||||
ui/status/volume.js \
|
ui/status/volume.js \
|
||||||
ui/status/bluetooth.js \
|
ui/status/bluetooth.js \
|
||||||
|
ui/status/screencast.js \
|
||||||
ui/status/system.js \
|
ui/status/system.js \
|
||||||
ui/switcherPopup.js \
|
ui/switcherPopup.js \
|
||||||
ui/tweener.js \
|
ui/tweener.js \
|
||||||
|
@ -28,6 +28,7 @@ const LoginManager = imports.misc.loginManager;
|
|||||||
const LookingGlass = imports.ui.lookingGlass;
|
const LookingGlass = imports.ui.lookingGlass;
|
||||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||||
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
|
||||||
|
const Screencast = imports.ui.screencast;
|
||||||
const ScreenShield = imports.ui.screenShield;
|
const ScreenShield = imports.ui.screenShield;
|
||||||
const Scripting = imports.ui.scripting;
|
const Scripting = imports.ui.scripting;
|
||||||
const SessionMode = imports.ui.sessionMode;
|
const SessionMode = imports.ui.sessionMode;
|
||||||
@ -59,6 +60,7 @@ let sessionMode = null;
|
|||||||
let shellDBusService = null;
|
let shellDBusService = null;
|
||||||
let shellMountOpDBusService = null;
|
let shellMountOpDBusService = null;
|
||||||
let screenSaverDBus = null;
|
let screenSaverDBus = null;
|
||||||
|
let screencastService = null;
|
||||||
let modalCount = 0;
|
let modalCount = 0;
|
||||||
let keybindingMode = Shell.KeyBindingMode.NONE;
|
let keybindingMode = Shell.KeyBindingMode.NONE;
|
||||||
let modalActorFocusStack = [];
|
let modalActorFocusStack = [];
|
||||||
@ -151,6 +153,7 @@ function _initializeUI() {
|
|||||||
// working until it's updated.
|
// working until it's updated.
|
||||||
uiGroup = layoutManager.uiGroup;
|
uiGroup = layoutManager.uiGroup;
|
||||||
|
|
||||||
|
screencastService = new Screencast.ScreencastService();
|
||||||
xdndHandler = new XdndHandler.XdndHandler();
|
xdndHandler = new XdndHandler.XdndHandler();
|
||||||
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
|
||||||
osdWindow = new OsdWindow.OsdWindow();
|
osdWindow = new OsdWindow.OsdWindow();
|
||||||
|
@ -815,7 +815,9 @@ const AggregateMenu = new Lang.Class({
|
|||||||
this._volume = new imports.ui.status.volume.Indicator();
|
this._volume = new imports.ui.status.volume.Indicator();
|
||||||
this._brightness = new imports.ui.status.brightness.Indicator();
|
this._brightness = new imports.ui.status.brightness.Indicator();
|
||||||
this._system = new imports.ui.status.system.Indicator();
|
this._system = new imports.ui.status.system.Indicator();
|
||||||
|
this._screencast = new imports.ui.status.screencast.Indicator();
|
||||||
|
|
||||||
|
this._indicators.add_child(this._screencast.indicators);
|
||||||
this._indicators.add_child(this._network.indicators);
|
this._indicators.add_child(this._network.indicators);
|
||||||
this._indicators.add_child(this._bluetooth.indicators);
|
this._indicators.add_child(this._bluetooth.indicators);
|
||||||
this._indicators.add_child(this._rfkill.indicators);
|
this._indicators.add_child(this._rfkill.indicators);
|
||||||
|
@ -4,6 +4,7 @@ const Gio = imports.gi.Gio;
|
|||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
const Shell = imports.gi.Shell;
|
const Shell = imports.gi.Shell;
|
||||||
|
const Signals = imports.signals;
|
||||||
|
|
||||||
const Hash = imports.misc.hash;
|
const Hash = imports.misc.hash;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
@ -44,6 +45,10 @@ const ScreencastService = new Lang.Class({
|
|||||||
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get isRecording() {
|
||||||
|
return this._recorders.size() > 0;
|
||||||
|
},
|
||||||
|
|
||||||
_ensureRecorderForSender: function(sender) {
|
_ensureRecorderForSender: function(sender) {
|
||||||
let recorder = this._recorders.get(sender);
|
let recorder = this._recorders.get(sender);
|
||||||
if (!recorder) {
|
if (!recorder) {
|
||||||
@ -52,6 +57,7 @@ const ScreencastService = new Lang.Class({
|
|||||||
Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
|
Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
|
||||||
Lang.bind(this, this._onNameVanished));
|
Lang.bind(this, this._onNameVanished));
|
||||||
this._recorders.set(sender, recorder);
|
this._recorders.set(sender, recorder);
|
||||||
|
this.emit('updated');
|
||||||
}
|
}
|
||||||
return recorder;
|
return recorder;
|
||||||
},
|
},
|
||||||
@ -62,6 +68,7 @@ const ScreencastService = new Lang.Class({
|
|||||||
|
|
||||||
for (let sender in this._recorders.keys())
|
for (let sender in this._recorders.keys())
|
||||||
this._recorders.delete(sender);
|
this._recorders.delete(sender);
|
||||||
|
this.emit('updated');
|
||||||
},
|
},
|
||||||
|
|
||||||
_onNameVanished: function(connection, name) {
|
_onNameVanished: function(connection, name) {
|
||||||
@ -76,6 +83,7 @@ const ScreencastService = new Lang.Class({
|
|||||||
Gio.bus_unwatch_name(recorder._watchNameId);
|
Gio.bus_unwatch_name(recorder._watchNameId);
|
||||||
recorder.close();
|
recorder.close();
|
||||||
this._recorders.delete(sender);
|
this._recorders.delete(sender);
|
||||||
|
this.emit('updated');
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -137,3 +145,4 @@ const ScreencastService = new Lang.Class({
|
|||||||
invocation.return_value(GLib.Variant.new('(b)', [success]));
|
invocation.return_value(GLib.Variant.new('(b)', [success]));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Signals.addSignalMethods(ScreencastService.prototype);
|
||||||
|
@ -12,7 +12,6 @@ const ExtensionDownloader = imports.ui.extensionDownloader;
|
|||||||
const ExtensionUtils = imports.misc.extensionUtils;
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
const Hash = imports.misc.hash;
|
const Hash = imports.misc.hash;
|
||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const Screencast = imports.ui.screencast;
|
|
||||||
const Screenshot = imports.ui.screenshot;
|
const Screenshot = imports.ui.screenshot;
|
||||||
|
|
||||||
const GnomeShellIface = <interface name="org.gnome.Shell">
|
const GnomeShellIface = <interface name="org.gnome.Shell">
|
||||||
@ -73,7 +72,6 @@ const GnomeShell = new Lang.Class({
|
|||||||
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
|
||||||
|
|
||||||
this._extensionsService = new GnomeShellExtensions();
|
this._extensionsService = new GnomeShellExtensions();
|
||||||
this._screencastService = new Screencast.ScreencastService();
|
|
||||||
this._screenshotService = new Screenshot.ScreenshotService();
|
this._screenshotService = new Screenshot.ScreenshotService();
|
||||||
|
|
||||||
this._grabbedAccelerators = new Hash.Map();
|
this._grabbedAccelerators = new Hash.Map();
|
||||||
|
26
js/ui/status/screencast.js
Normal file
26
js/ui/status/screencast.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
|
const Lang = imports.lang;
|
||||||
|
|
||||||
|
const Main = imports.ui.main;
|
||||||
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
|
||||||
|
const Indicator = new Lang.Class({
|
||||||
|
Name: 'ScreencastIndicator',
|
||||||
|
Extends: PanelMenu.SystemIndicator,
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
this.parent();
|
||||||
|
|
||||||
|
this._indicator = this._addIndicator();
|
||||||
|
this._indicator.icon_name = 'media-record-symbolic';
|
||||||
|
this._indicator.add_style_class_name('screencast-indicator');
|
||||||
|
this._sync();
|
||||||
|
|
||||||
|
Main.screencastService.connect('updated', Lang.bind(this, this._sync));
|
||||||
|
},
|
||||||
|
|
||||||
|
_sync: function() {
|
||||||
|
this._indicator.visible = Main.screencastService.isRecording;
|
||||||
|
},
|
||||||
|
});
|
@ -69,16 +69,12 @@ struct _ShellRecorder {
|
|||||||
|
|
||||||
int xinput_opcode;
|
int xinput_opcode;
|
||||||
|
|
||||||
CoglHandle recording_icon; /* icon shown while playing */
|
|
||||||
|
|
||||||
GSettings *a11y_settings;
|
GSettings *a11y_settings;
|
||||||
gboolean draw_cursor;
|
gboolean draw_cursor;
|
||||||
cairo_surface_t *cursor_image;
|
cairo_surface_t *cursor_image;
|
||||||
int cursor_hot_x;
|
int cursor_hot_x;
|
||||||
int cursor_hot_y;
|
int cursor_hot_y;
|
||||||
|
|
||||||
gboolean only_paint; /* Used to temporarily suppress recording */
|
|
||||||
|
|
||||||
int framerate;
|
int framerate;
|
||||||
char *pipeline_description;
|
char *pipeline_description;
|
||||||
char *file_template;
|
char *file_template;
|
||||||
@ -170,57 +166,6 @@ G_DEFINE_TYPE(ShellRecorder, shell_recorder, G_TYPE_OBJECT);
|
|||||||
*/
|
*/
|
||||||
#define DEFAULT_MEMORY_TARGET (512*1024)
|
#define DEFAULT_MEMORY_TARGET (512*1024)
|
||||||
|
|
||||||
/* Create an emblem to show at the lower-left corner of the stage while
|
|
||||||
* recording. The emblem is drawn *after* we record the frame so doesn't
|
|
||||||
* show up in the frame.
|
|
||||||
*/
|
|
||||||
static CoglHandle
|
|
||||||
create_recording_icon (void)
|
|
||||||
{
|
|
||||||
cairo_surface_t *surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 32, 32);
|
|
||||||
cairo_t *cr;
|
|
||||||
cairo_pattern_t *pat;
|
|
||||||
CoglHandle texture;
|
|
||||||
|
|
||||||
cr = cairo_create (surface);
|
|
||||||
|
|
||||||
/* clear to transparent */
|
|
||||||
cairo_save (cr);
|
|
||||||
cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_restore (cr);
|
|
||||||
|
|
||||||
/* radial "glow" */
|
|
||||||
pat = cairo_pattern_create_radial (16, 16, 6,
|
|
||||||
16, 16, 14);
|
|
||||||
cairo_pattern_add_color_stop_rgba (pat, 0.0,
|
|
||||||
1, 0, 0, 1); /* opaque red */
|
|
||||||
cairo_pattern_add_color_stop_rgba (pat, 1.0,
|
|
||||||
1, 0, 0, 0); /* transparent red */
|
|
||||||
|
|
||||||
cairo_set_source (cr, pat);
|
|
||||||
cairo_paint (cr);
|
|
||||||
cairo_pattern_destroy (pat);
|
|
||||||
|
|
||||||
/* red circle */
|
|
||||||
cairo_arc (cr, 16, 16, 8,
|
|
||||||
0, 2 * M_PI);
|
|
||||||
cairo_set_source_rgb (cr, 1, 0, 0);
|
|
||||||
cairo_fill (cr);
|
|
||||||
|
|
||||||
cairo_destroy (cr);
|
|
||||||
|
|
||||||
texture = cogl_texture_new_from_data (32, 32,
|
|
||||||
COGL_TEXTURE_NONE,
|
|
||||||
CLUTTER_CAIRO_FORMAT_ARGB32,
|
|
||||||
COGL_PIXEL_FORMAT_ANY,
|
|
||||||
cairo_image_surface_get_stride (surface),
|
|
||||||
cairo_image_surface_get_data (surface));
|
|
||||||
cairo_surface_destroy (surface);
|
|
||||||
|
|
||||||
return texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
static guint
|
static guint
|
||||||
get_memory_target (void)
|
get_memory_target (void)
|
||||||
{
|
{
|
||||||
@ -278,7 +223,6 @@ shell_recorder_init (ShellRecorder *recorder)
|
|||||||
|
|
||||||
recorder->gdk_screen = gdk_screen_get_default ();
|
recorder->gdk_screen = gdk_screen_get_default ();
|
||||||
|
|
||||||
recorder->recording_icon = create_recording_icon ();
|
|
||||||
recorder->memory_target = get_memory_target();
|
recorder->memory_target = get_memory_target();
|
||||||
|
|
||||||
recorder->a11y_settings = g_settings_new (A11Y_APPS_SCHEMA);
|
recorder->a11y_settings = g_settings_new (A11Y_APPS_SCHEMA);
|
||||||
@ -303,8 +247,6 @@ shell_recorder_finalize (GObject *object)
|
|||||||
recorder_set_pipeline (recorder, NULL);
|
recorder_set_pipeline (recorder, NULL);
|
||||||
recorder_set_file_template (recorder, NULL);
|
recorder_set_file_template (recorder, NULL);
|
||||||
|
|
||||||
cogl_handle_unref (recorder->recording_icon);
|
|
||||||
|
|
||||||
g_clear_object (&recorder->a11y_settings);
|
g_clear_object (&recorder->a11y_settings);
|
||||||
|
|
||||||
G_OBJECT_CLASS (shell_recorder_parent_class)->finalize (object);
|
G_OBJECT_CLASS (shell_recorder_parent_class)->finalize (object);
|
||||||
@ -340,20 +282,7 @@ recorder_update_memory_used (ShellRecorder *recorder,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (memory_used != recorder->memory_used)
|
if (memory_used != recorder->memory_used)
|
||||||
{
|
|
||||||
recorder->memory_used = memory_used;
|
recorder->memory_used = memory_used;
|
||||||
if (repaint)
|
|
||||||
{
|
|
||||||
/* In other cases we just queue a redraw even if we only need
|
|
||||||
* to repaint and not redraw a frame, but having changes in
|
|
||||||
* memory usage cause frames to be painted and memory used
|
|
||||||
* seems like a bad idea.
|
|
||||||
*/
|
|
||||||
recorder->only_paint = TRUE;
|
|
||||||
clutter_stage_ensure_redraw (recorder->stage);
|
|
||||||
recorder->only_paint = FALSE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Timeout used to avoid not drawing for more than MAXIMUM_PAUSE_TIME
|
/* Timeout used to avoid not drawing for more than MAXIMUM_PAUSE_TIME
|
||||||
@ -475,56 +404,6 @@ recorder_draw_cursor (ShellRecorder *recorder,
|
|||||||
gst_buffer_unmap (buffer, &info);
|
gst_buffer_unmap (buffer, &info);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Draw an overlay indicating how much of the target memory is used
|
|
||||||
* for buffering frames.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
recorder_draw_buffer_meter (ShellRecorder *recorder)
|
|
||||||
{
|
|
||||||
int fill_level;
|
|
||||||
GdkRectangle primary_monitor;
|
|
||||||
float rects[16];
|
|
||||||
|
|
||||||
gdk_screen_get_monitor_workarea (recorder->gdk_screen,
|
|
||||||
gdk_screen_get_primary_monitor (recorder->gdk_screen),
|
|
||||||
&primary_monitor);
|
|
||||||
|
|
||||||
recorder_update_memory_used (recorder, FALSE);
|
|
||||||
|
|
||||||
/* As the buffer gets more full, we go from green, to yellow, to red */
|
|
||||||
if (recorder->memory_used > (recorder->memory_target * 3) / 4)
|
|
||||||
cogl_set_source_color4f (1, 0, 0, 1);
|
|
||||||
else if (recorder->memory_used > recorder->memory_target / 2)
|
|
||||||
cogl_set_source_color4f (1, 1, 0, 1);
|
|
||||||
else
|
|
||||||
cogl_set_source_color4f (0, 1, 0, 1);
|
|
||||||
|
|
||||||
fill_level = MIN (60, (recorder->memory_used * 60) / recorder->memory_target);
|
|
||||||
|
|
||||||
/* A hollow rectangle filled from the left to fill_level */
|
|
||||||
rects[0] = primary_monitor.x + primary_monitor.width - 64;
|
|
||||||
rects[1] = primary_monitor.y + primary_monitor.height - 10;
|
|
||||||
rects[2] = primary_monitor.x + primary_monitor.width - 2;
|
|
||||||
rects[3] = primary_monitor.y + primary_monitor.height - 9;
|
|
||||||
|
|
||||||
rects[4] = primary_monitor.x + primary_monitor.width - 64;
|
|
||||||
rects[5] = primary_monitor.y + primary_monitor.height - 9;
|
|
||||||
rects[6] = primary_monitor.x + primary_monitor.width - (63 - fill_level);
|
|
||||||
rects[7] = primary_monitor.y + primary_monitor.height - 3;
|
|
||||||
|
|
||||||
rects[8] = primary_monitor.x + primary_monitor.width - 3;
|
|
||||||
rects[9] = primary_monitor.y + primary_monitor.height - 9;
|
|
||||||
rects[10] = primary_monitor.x + primary_monitor.width - 2;
|
|
||||||
rects[11] = primary_monitor.y + primary_monitor.height - 3;
|
|
||||||
|
|
||||||
rects[12] = primary_monitor.x + primary_monitor.width - 64;
|
|
||||||
rects[13] = primary_monitor.y + primary_monitor.height - 3;
|
|
||||||
rects[14] = primary_monitor.x + primary_monitor.width - 2;
|
|
||||||
rects[15] = primary_monitor.y + primary_monitor.height - 2;
|
|
||||||
|
|
||||||
cogl_rectangles (rects, 4);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* We want to time-stamp each frame based on the actual time it was
|
/* We want to time-stamp each frame based on the actual time it was
|
||||||
* recorded. We probably should use the pipeline clock rather than
|
* recorded. We probably should use the pipeline clock rather than
|
||||||
* gettimeofday(): that would be needed to get sync'ed audio correct.
|
* gettimeofday(): that would be needed to get sync'ed audio correct.
|
||||||
@ -608,22 +487,7 @@ recorder_on_stage_paint (ClutterActor *actor,
|
|||||||
ShellRecorder *recorder)
|
ShellRecorder *recorder)
|
||||||
{
|
{
|
||||||
if (recorder->state == RECORDER_STATE_RECORDING)
|
if (recorder->state == RECORDER_STATE_RECORDING)
|
||||||
{
|
|
||||||
GdkRectangle primary_monitor;
|
|
||||||
|
|
||||||
gdk_screen_get_monitor_workarea (recorder->gdk_screen,
|
|
||||||
gdk_screen_get_primary_monitor (recorder->gdk_screen),
|
|
||||||
&primary_monitor);
|
|
||||||
if (!recorder->only_paint)
|
|
||||||
recorder_record_frame (recorder);
|
recorder_record_frame (recorder);
|
||||||
|
|
||||||
cogl_set_source_texture (recorder->recording_icon);
|
|
||||||
cogl_rectangle (primary_monitor.x + primary_monitor.width - 32, primary_monitor.y + primary_monitor.height - 42,
|
|
||||||
primary_monitor.x + primary_monitor.width, primary_monitor.y + primary_monitor.height - 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recorder->state == RECORDER_STATE_RECORDING || recorder->memory_used != 0)
|
|
||||||
recorder_draw_buffer_meter (recorder);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
Loading…
Reference in New Issue
Block a user