ShellRecorder: update to use MetaCursorTracker
Replace more direct XFixes usage with a the appropriate abstraction API from mutter, which is guaranteed to work in wayland too. It doesn't yet replace pointer position tracking, although probably it should. Also, because now we're using Mutter API, we lose the standalone test case. https://bugzilla.gnome.org/show_bug.cgi?id=705911
This commit is contained in:
parent
2b8414a453
commit
d52c95a15f
@ -52,7 +52,8 @@ const ScreencastService = new Lang.Class({
|
|||||||
_ensureRecorderForSender: function(sender) {
|
_ensureRecorderForSender: function(sender) {
|
||||||
let recorder = this._recorders.get(sender);
|
let recorder = this._recorders.get(sender);
|
||||||
if (!recorder) {
|
if (!recorder) {
|
||||||
recorder = new Shell.Recorder({ stage: global.stage });
|
recorder = new Shell.Recorder({ stage: global.stage,
|
||||||
|
screen: global.screen });
|
||||||
recorder._watchNameId =
|
recorder._watchNameId =
|
||||||
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));
|
||||||
|
@ -226,14 +226,6 @@ shell_recorder_private_sources = \
|
|||||||
|
|
||||||
shell_private_sources += $(shell_recorder_private_sources)
|
shell_private_sources += $(shell_recorder_private_sources)
|
||||||
|
|
||||||
noinst_PROGRAMS += test-recorder
|
|
||||||
|
|
||||||
test_recorder_CPPFLAGS = $(TEST_SHELL_RECORDER_CFLAGS)
|
|
||||||
test_recorder_LDADD = $(TEST_SHELL_RECORDER_LIBS)
|
|
||||||
|
|
||||||
test_recorder_SOURCES = \
|
|
||||||
$(shell_recorder_sources) $(shell_recorder_private_sources) \
|
|
||||||
test-recorder.c
|
|
||||||
endif BUILD_RECORDER
|
endif BUILD_RECORDER
|
||||||
|
|
||||||
########################################
|
########################################
|
||||||
|
@ -14,11 +14,14 @@
|
|||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
#include <gdk/gdk.h>
|
#include <gdk/gdk.h>
|
||||||
|
|
||||||
|
#include <cogl/cogl.h>
|
||||||
|
#include <meta/screen.h>
|
||||||
|
#include <meta/meta-cursor-tracker.h>
|
||||||
|
|
||||||
#include "shell-recorder-src.h"
|
#include "shell-recorder-src.h"
|
||||||
#include "shell-recorder.h"
|
#include "shell-recorder.h"
|
||||||
|
|
||||||
#include <clutter/x11/clutter-x11.h>
|
#include <clutter/x11/clutter-x11.h>
|
||||||
#include <X11/extensions/Xfixes.h>
|
|
||||||
#include <X11/extensions/XInput.h>
|
#include <X11/extensions/XInput.h>
|
||||||
#include <X11/extensions/XInput2.h>
|
#include <X11/extensions/XInput2.h>
|
||||||
|
|
||||||
@ -64,14 +67,13 @@ struct _ShellRecorder {
|
|||||||
int pointer_x;
|
int pointer_x;
|
||||||
int pointer_y;
|
int pointer_y;
|
||||||
|
|
||||||
gboolean have_xfixes;
|
|
||||||
int xfixes_event_base;
|
|
||||||
|
|
||||||
int xinput_opcode;
|
int xinput_opcode;
|
||||||
|
|
||||||
GSettings *a11y_settings;
|
GSettings *a11y_settings;
|
||||||
gboolean draw_cursor;
|
gboolean draw_cursor;
|
||||||
|
MetaCursorTracker *cursor_tracker;
|
||||||
cairo_surface_t *cursor_image;
|
cairo_surface_t *cursor_image;
|
||||||
|
guint8 *cursor_memory;
|
||||||
int cursor_hot_x;
|
int cursor_hot_x;
|
||||||
int cursor_hot_y;
|
int cursor_hot_y;
|
||||||
|
|
||||||
@ -121,6 +123,7 @@ static void recorder_pipeline_closed (RecorderPipeline *pipeline);
|
|||||||
|
|
||||||
enum {
|
enum {
|
||||||
PROP_0,
|
PROP_0,
|
||||||
|
PROP_SCREEN,
|
||||||
PROP_STAGE,
|
PROP_STAGE,
|
||||||
PROP_FRAMERATE,
|
PROP_FRAMERATE,
|
||||||
PROP_PIPELINE,
|
PROP_PIPELINE,
|
||||||
@ -242,6 +245,8 @@ shell_recorder_finalize (GObject *object)
|
|||||||
|
|
||||||
if (recorder->cursor_image)
|
if (recorder->cursor_image)
|
||||||
cairo_surface_destroy (recorder->cursor_image);
|
cairo_surface_destroy (recorder->cursor_image);
|
||||||
|
if (recorder->cursor_memory)
|
||||||
|
g_free (recorder->cursor_memory);
|
||||||
|
|
||||||
recorder_set_stage (recorder, NULL);
|
recorder_set_stage (recorder, NULL);
|
||||||
recorder_set_pipeline (recorder, NULL);
|
recorder_set_pipeline (recorder, NULL);
|
||||||
@ -322,38 +327,24 @@ recorder_remove_redraw_timeout (ShellRecorder *recorder)
|
|||||||
static void
|
static void
|
||||||
recorder_fetch_cursor_image (ShellRecorder *recorder)
|
recorder_fetch_cursor_image (ShellRecorder *recorder)
|
||||||
{
|
{
|
||||||
XFixesCursorImage *cursor_image;
|
CoglTexture *texture;
|
||||||
guchar *data;
|
int width, height;
|
||||||
int stride;
|
int stride;
|
||||||
int i, j;
|
guint8 *data;
|
||||||
|
|
||||||
if (!recorder->have_xfixes)
|
texture = meta_cursor_tracker_get_sprite (recorder->cursor_tracker);
|
||||||
return;
|
width = cogl_texture_get_width (texture);
|
||||||
|
height = cogl_texture_get_height (texture);
|
||||||
|
stride = 4 * width;
|
||||||
|
data = g_new (guint8, stride * height);
|
||||||
|
cogl_texture_get_data (texture, CLUTTER_CAIRO_FORMAT_ARGB32, stride, data);
|
||||||
|
|
||||||
cursor_image = XFixesGetCursorImage (clutter_x11_get_default_display ());
|
/* FIXME: cairo-gl? */
|
||||||
if (!cursor_image)
|
recorder->cursor_image = cairo_image_surface_create_for_data (data,
|
||||||
return;
|
CAIRO_FORMAT_ARGB32,
|
||||||
|
width, height,
|
||||||
recorder->cursor_hot_x = cursor_image->xhot;
|
stride);
|
||||||
recorder->cursor_hot_y = cursor_image->yhot;
|
recorder->cursor_memory = data;
|
||||||
|
|
||||||
recorder->cursor_image = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
|
||||||
cursor_image->width,
|
|
||||||
cursor_image->height);
|
|
||||||
|
|
||||||
/* The pixel data (in typical Xlib breakage) is longs even on
|
|
||||||
* 64-bit platforms, so we have to data-convert there. For simplicity,
|
|
||||||
* just do it always
|
|
||||||
*/
|
|
||||||
data = cairo_image_surface_get_data (recorder->cursor_image);
|
|
||||||
stride = cairo_image_surface_get_stride (recorder->cursor_image);
|
|
||||||
for (i = 0; i < cursor_image->height; i++)
|
|
||||||
for (j = 0; j < cursor_image->width; j++)
|
|
||||||
*(guint32 *)(data + i * stride + 4 * j) = cursor_image->pixels[i * cursor_image->width + j];
|
|
||||||
|
|
||||||
cairo_surface_mark_dirty (recorder->cursor_image);
|
|
||||||
|
|
||||||
XFree (cursor_image);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Overlay the cursor image on the frame. We draw the cursor image
|
/* Overlay the cursor image on the frame. We draw the cursor image
|
||||||
@ -546,6 +537,24 @@ recorder_queue_redraw (ShellRecorder *recorder)
|
|||||||
recorder_idle_redraw, recorder, NULL);
|
recorder_idle_redraw, recorder, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_cursor_changed (MetaCursorTracker *tracker,
|
||||||
|
ShellRecorder *recorder)
|
||||||
|
{
|
||||||
|
if (recorder->cursor_image)
|
||||||
|
{
|
||||||
|
cairo_surface_destroy (recorder->cursor_image);
|
||||||
|
recorder->cursor_image = NULL;
|
||||||
|
}
|
||||||
|
if (recorder->cursor_memory)
|
||||||
|
{
|
||||||
|
g_free (recorder->cursor_memory);
|
||||||
|
recorder->cursor_memory = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder_queue_redraw (recorder);
|
||||||
|
}
|
||||||
|
|
||||||
/* We use an event filter on the stage to get the XFixesCursorNotifyEvent
|
/* We use an event filter on the stage to get the XFixesCursorNotifyEvent
|
||||||
* and also to track cursor position (when the cursor is over the stage's
|
* and also to track cursor position (when the cursor is over the stage's
|
||||||
* input area); tracking cursor position here rather than with ClutterEvent
|
* input area); tracking cursor position here rather than with ClutterEvent
|
||||||
@ -567,23 +576,8 @@ recorder_event_filter (XEvent *xev,
|
|||||||
xev->xcookie.extension == recorder->xinput_opcode)
|
xev->xcookie.extension == recorder->xinput_opcode)
|
||||||
input_event = (XIEvent *) xev->xcookie.data;
|
input_event = (XIEvent *) xev->xcookie.data;
|
||||||
|
|
||||||
if (xev->xany.type == recorder->xfixes_event_base + XFixesCursorNotify)
|
if (input_event != NULL &&
|
||||||
{
|
input_event->evtype == XI_Motion)
|
||||||
XFixesCursorNotifyEvent *notify_event = (XFixesCursorNotifyEvent *)xev;
|
|
||||||
|
|
||||||
if (notify_event->subtype == XFixesDisplayCursorNotify)
|
|
||||||
{
|
|
||||||
if (recorder->cursor_image)
|
|
||||||
{
|
|
||||||
cairo_surface_destroy (recorder->cursor_image);
|
|
||||||
recorder->cursor_image = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
recorder_queue_redraw (recorder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (input_event != NULL &&
|
|
||||||
input_event->evtype == XI_Motion)
|
|
||||||
{
|
{
|
||||||
XIDeviceEvent *device_event = (XIDeviceEvent *) input_event;
|
XIDeviceEvent *device_event = (XIDeviceEvent *) input_event;
|
||||||
if (device_event->deviceid == VIRTUAL_CORE_POINTER_ID)
|
if (device_event->deviceid == VIRTUAL_CORE_POINTER_ID)
|
||||||
@ -807,14 +801,6 @@ recorder_set_stage (ShellRecorder *recorder,
|
|||||||
|
|
||||||
recorder_update_size (recorder);
|
recorder_update_size (recorder);
|
||||||
|
|
||||||
recorder->have_xfixes = XFixesQueryExtension (clutter_x11_get_default_display (),
|
|
||||||
&recorder->xfixes_event_base,
|
|
||||||
&error_base);
|
|
||||||
if (recorder->have_xfixes)
|
|
||||||
XFixesSelectCursorInput (clutter_x11_get_default_display (),
|
|
||||||
clutter_x11_get_stage_window (stage),
|
|
||||||
XFixesDisplayCursorNotifyMask);
|
|
||||||
|
|
||||||
if (XQueryExtension (clutter_x11_get_default_display (),
|
if (XQueryExtension (clutter_x11_get_default_display (),
|
||||||
"XInputExtension",
|
"XInputExtension",
|
||||||
&recorder->xinput_opcode,
|
&recorder->xinput_opcode,
|
||||||
@ -843,6 +829,22 @@ recorder_set_stage (ShellRecorder *recorder,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
recorder_set_screen (ShellRecorder *recorder,
|
||||||
|
MetaScreen *screen)
|
||||||
|
{
|
||||||
|
MetaCursorTracker *tracker;
|
||||||
|
|
||||||
|
tracker = meta_cursor_tracker_get_for_screen (screen);
|
||||||
|
|
||||||
|
if (tracker == recorder->cursor_tracker)
|
||||||
|
return;
|
||||||
|
|
||||||
|
recorder->cursor_tracker = tracker;
|
||||||
|
g_signal_connect_object (tracker, "cursor-changed",
|
||||||
|
G_CALLBACK (on_cursor_changed), recorder, 0);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
recorder_set_framerate (ShellRecorder *recorder,
|
recorder_set_framerate (ShellRecorder *recorder,
|
||||||
int framerate)
|
int framerate)
|
||||||
@ -918,6 +920,9 @@ shell_recorder_set_property (GObject *object,
|
|||||||
|
|
||||||
switch (prop_id)
|
switch (prop_id)
|
||||||
{
|
{
|
||||||
|
case PROP_SCREEN:
|
||||||
|
recorder_set_screen (recorder, g_value_get_object (value));
|
||||||
|
break;
|
||||||
case PROP_STAGE:
|
case PROP_STAGE:
|
||||||
recorder_set_stage (recorder, g_value_get_object (value));
|
recorder_set_stage (recorder, g_value_get_object (value));
|
||||||
break;
|
break;
|
||||||
@ -979,6 +984,14 @@ shell_recorder_class_init (ShellRecorderClass *klass)
|
|||||||
gobject_class->get_property = shell_recorder_get_property;
|
gobject_class->get_property = shell_recorder_get_property;
|
||||||
gobject_class->set_property = shell_recorder_set_property;
|
gobject_class->set_property = shell_recorder_set_property;
|
||||||
|
|
||||||
|
g_object_class_install_property (gobject_class,
|
||||||
|
PROP_SCREEN,
|
||||||
|
g_param_spec_object ("screen",
|
||||||
|
"Screen",
|
||||||
|
"Screen to record",
|
||||||
|
META_TYPE_SCREEN,
|
||||||
|
G_PARAM_WRITABLE));
|
||||||
|
|
||||||
g_object_class_install_property (gobject_class,
|
g_object_class_install_property (gobject_class,
|
||||||
PROP_STAGE,
|
PROP_STAGE,
|
||||||
g_param_spec_object ("stage",
|
g_param_spec_object ("stage",
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
|
||||||
|
|
||||||
#define GST_USE_UNSTABLE_API
|
|
||||||
#include "shell-recorder.h"
|
|
||||||
#include <clutter/clutter.h>
|
|
||||||
#include <gtk/gtk.h>
|
|
||||||
#include <gst/gst.h>
|
|
||||||
|
|
||||||
/* Very simple test of the ShellRecorder class; shows some text strings
|
|
||||||
* moving around and records it.
|
|
||||||
*/
|
|
||||||
static ShellRecorder *recorder = NULL;
|
|
||||||
|
|
||||||
static gboolean
|
|
||||||
stop_recording_timeout (ClutterActor *stage)
|
|
||||||
{
|
|
||||||
if (recorder)
|
|
||||||
{
|
|
||||||
shell_recorder_close (recorder);
|
|
||||||
|
|
||||||
/* quit when the recorder finishes closing
|
|
||||||
*/
|
|
||||||
g_object_weak_ref (G_OBJECT (recorder),
|
|
||||||
(GWeakNotify)
|
|
||||||
clutter_actor_destroy,
|
|
||||||
stage);
|
|
||||||
|
|
||||||
g_object_unref (recorder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
clutter_actor_destroy (stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
on_animation_completed (ClutterAnimation *animation,
|
|
||||||
ClutterStage *stage)
|
|
||||||
{
|
|
||||||
g_timeout_add (1000, (GSourceFunc) stop_recording_timeout, stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
on_stage_realized (ClutterActor *stage,
|
|
||||||
gpointer data)
|
|
||||||
{
|
|
||||||
recorder = shell_recorder_new (CLUTTER_STAGE (stage));
|
|
||||||
shell_recorder_set_file_template (recorder, "test-recorder.webm");
|
|
||||||
shell_recorder_record (recorder, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main (int argc, char **argv)
|
|
||||||
{
|
|
||||||
ClutterActor *stage;
|
|
||||||
ClutterActor *text;
|
|
||||||
ClutterAnimation *animation;
|
|
||||||
ClutterColor red, green, blue;
|
|
||||||
|
|
||||||
gtk_init (&argc, &argv);
|
|
||||||
gst_init (&argc, &argv);
|
|
||||||
if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
clutter_color_from_string (&red, "red");
|
|
||||||
clutter_color_from_string (&green, "green");
|
|
||||||
clutter_color_from_string (&blue, "blue");
|
|
||||||
stage = clutter_stage_new ();
|
|
||||||
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
|
|
||||||
|
|
||||||
text = g_object_new (CLUTTER_TYPE_TEXT,
|
|
||||||
"text", "Red",
|
|
||||||
"font-name", "Sans 40px",
|
|
||||||
"color", &red,
|
|
||||||
NULL);
|
|
||||||
clutter_actor_add_child (stage, text);
|
|
||||||
animation = clutter_actor_animate (text,
|
|
||||||
CLUTTER_EASE_IN_OUT_QUAD,
|
|
||||||
3000,
|
|
||||||
"x", 320.0,
|
|
||||||
"y", 240.0,
|
|
||||||
NULL);
|
|
||||||
g_signal_connect (animation, "completed",
|
|
||||||
G_CALLBACK (on_animation_completed), stage);
|
|
||||||
|
|
||||||
text = g_object_new (CLUTTER_TYPE_TEXT,
|
|
||||||
"text", "Blue",
|
|
||||||
"font-name", "Sans 40px",
|
|
||||||
"color", &blue,
|
|
||||||
"x", 640.0,
|
|
||||||
"y", 0.0,
|
|
||||||
NULL);
|
|
||||||
clutter_actor_set_anchor_point_from_gravity (text, CLUTTER_GRAVITY_NORTH_EAST);
|
|
||||||
clutter_actor_add_child (stage, text);
|
|
||||||
animation = clutter_actor_animate (text,
|
|
||||||
CLUTTER_EASE_IN_OUT_QUAD,
|
|
||||||
3000,
|
|
||||||
"x", 320.0,
|
|
||||||
"y", 240.0,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
text = g_object_new (CLUTTER_TYPE_TEXT,
|
|
||||||
"text", "Green",
|
|
||||||
"font-name", "Sans 40px",
|
|
||||||
"color", &green,
|
|
||||||
"x", 0.0,
|
|
||||||
"y", 480.0,
|
|
||||||
NULL);
|
|
||||||
clutter_actor_set_anchor_point_from_gravity (text, CLUTTER_GRAVITY_SOUTH_WEST);
|
|
||||||
clutter_actor_add_child (stage, text);
|
|
||||||
animation = clutter_actor_animate (text,
|
|
||||||
CLUTTER_EASE_IN_OUT_QUAD,
|
|
||||||
3000,
|
|
||||||
"x", 320.0,
|
|
||||||
"y", 240.0,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
g_signal_connect_after (stage, "realize",
|
|
||||||
G_CALLBACK (on_stage_realized), NULL);
|
|
||||||
|
|
||||||
clutter_actor_show (stage);
|
|
||||||
|
|
||||||
clutter_main ();
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user