mirror of https://code.videolan.org/videolan/vlc
wl_screenshooter: Wayland screen grabber
This commit is contained in:
parent
fa446c6554
commit
cc79ed90a9
1
NEWS
1
NEWS
|
@ -4,6 +4,7 @@ Changes between 2.2.x and 3.0.0-git:
|
|||
Access:
|
||||
* Support HDS (Http Dynamic Streaming) from Adobe (f4m, f4v, etc.)
|
||||
* New SMB access module using libdsm
|
||||
* Screen capture plugin for Wayland display
|
||||
* Support decompression and extraction through libarchive (tar, zip, rar...)
|
||||
* Improvements of cookie handling (share cookies between playlist items,
|
||||
domain / path matching, Secure cookies)
|
||||
|
|
|
@ -419,6 +419,7 @@ $Id$
|
|||
* windrive: Windows logical disc drives
|
||||
* wingdi: WIN 32 / WIN CE GDI video output
|
||||
* winstore: Windows Store App audio output
|
||||
* wl_screenshooter: Wayland screen capture input
|
||||
* wl_shell_surface: Wayland shell surface window provider
|
||||
* wl_shm: Wayland shared memory video output
|
||||
* wma_fixed: wma decoder using integer decoder from Rockbox
|
||||
|
|
|
@ -198,6 +198,22 @@ if HAVE_XCB
|
|||
access_LTLIBRARIES += libxcb_screen_plugin.la
|
||||
endif
|
||||
|
||||
libwl_screenshooter_plugin_la_DEPENDENCIES = \
|
||||
access/screen/screenshooter-client-protocol.h
|
||||
libwl_screenshooter_plugin_la_SOURCES = \
|
||||
access/screen/wayland.c
|
||||
nodist_libwl_screenshooter_plugin_la_SOURCES = \
|
||||
access/screen/screenshooter-protocol.c
|
||||
libwl_screenshooter_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) -Iaccess/screen
|
||||
libwl_screenshooter_plugin_la_CFLAGS = $(WAYLAND_CLIENT_CFLAGS)
|
||||
libwl_screenshooter_plugin_la_LIBADD = $(WAYLAND_CLIENT_LIBS) \
|
||||
$(LIBPTHREAD) $(LIBM)
|
||||
EXTRA_DIST += access/screen/screenshooter.xml
|
||||
CLEANFILES += $(nodist_libwl_screenshooter_plugin_la_SOURCES)
|
||||
if HAVE_WAYLAND
|
||||
access_LTLIBRARIES += libwl_screenshooter_plugin.la
|
||||
endif
|
||||
|
||||
libscreen_plugin_la_SOURCES = access/screen/screen.c access/screen/screen.h
|
||||
libscreen_plugin_la_LDFLAGS = $(AM_LDFLAGS)
|
||||
if HAVE_WIN32
|
||||
|
|
|
@ -0,0 +1,513 @@
|
|||
/**
|
||||
* @file wayland.c
|
||||
* @brief Wayland screenshooter extension module for VLC media player
|
||||
*/
|
||||
/*****************************************************************************
|
||||
* Copyright © 2014 Rémi Denis-Courmont
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Lesser General Public License as published by
|
||||
* the Free Software Foundation; either version 2.1 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License
|
||||
* along with this program; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
||||
*****************************************************************************/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <wayland-client.h>
|
||||
#include "screenshooter-client-protocol.h"
|
||||
|
||||
#include <vlc_common.h>
|
||||
#include <vlc_demux.h>
|
||||
#include <vlc_plugin.h>
|
||||
|
||||
struct demux_sys_t
|
||||
{
|
||||
struct wl_display *display;
|
||||
struct wl_output *output;
|
||||
struct wl_shm *shm;
|
||||
struct screenshooter *screenshooter;
|
||||
es_out_id_t *es;
|
||||
|
||||
long pagemask;
|
||||
float rate;
|
||||
int32_t x; /*< Requested horizontal offset */
|
||||
int32_t y; /*< Requested vertical offset */
|
||||
int32_t w; /*< Requested width */
|
||||
int32_t h; /*< Requested height */
|
||||
int32_t width; /*< Actual width */
|
||||
int32_t height; /*< Actual height */
|
||||
|
||||
bool done;
|
||||
mtime_t start;
|
||||
|
||||
vlc_thread_t thread;
|
||||
};
|
||||
|
||||
static bool DisplayError(vlc_object_t *obj, struct wl_display *display)
|
||||
{
|
||||
int val = wl_display_get_error(display);
|
||||
if (val == 0)
|
||||
return false;
|
||||
|
||||
if (val == EPROTO)
|
||||
{
|
||||
const struct wl_interface *iface;
|
||||
uint32_t id;
|
||||
|
||||
val = wl_display_get_protocol_error(display, &iface, &id);
|
||||
msg_Err(obj, "display protocol error %d on %s object %"PRIu32,
|
||||
val, iface->name, id);
|
||||
}
|
||||
else
|
||||
msg_Err(obj, "display fatal error: %s", vlc_strerror_c(val));
|
||||
|
||||
return true;
|
||||
}
|
||||
#define DisplayError(o,d) DisplayError(VLC_OBJECT(o), (d))
|
||||
|
||||
static void output_geometry_cb(void *data, struct wl_output *output, int32_t x,
|
||||
int32_t y, int32_t width, int32_t height,
|
||||
int32_t subpixel, const char *vendor,
|
||||
const char *model, int32_t transform)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
|
||||
msg_Dbg(demux, "output geometry: %s %s %"PRId32"x%"PRId32"mm "
|
||||
"@ %"PRId32"x%"PRId32" subpixel: %"PRId32" transform: %"PRId32,
|
||||
vendor, model, width, height, x, y, subpixel, transform);
|
||||
(void) output;
|
||||
}
|
||||
|
||||
static void output_mode_cb(void *data, struct wl_output *output,
|
||||
uint32_t flags, int32_t width, int32_t height,
|
||||
int32_t refresh)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
|
||||
msg_Dbg(demux, "output mode: 0x%08"PRIX32" %"PRId32"x%"PRId32
|
||||
" %"PRId32"mHz%s", flags, width, height, refresh,
|
||||
(flags & WL_OUTPUT_MODE_CURRENT) ? " (current)" : "");
|
||||
|
||||
if (!(flags & WL_OUTPUT_MODE_CURRENT))
|
||||
return;
|
||||
if (width <= sys->x || height <= sys->y)
|
||||
return;
|
||||
|
||||
if (sys->es != NULL)
|
||||
es_out_Del(demux->out, sys->es);
|
||||
|
||||
es_format_t fmt;
|
||||
|
||||
es_format_Init(&fmt, VIDEO_ES, VLC_CODEC_RGB32);
|
||||
fmt.video.i_chroma = VLC_CODEC_RGB32;
|
||||
fmt.video.i_bits_per_pixel = 32;
|
||||
fmt.video.i_sar_num = fmt.video.i_sar_den = 1;
|
||||
fmt.video.i_frame_rate = lroundf(1000.f * sys->rate);
|
||||
fmt.video.i_frame_rate_base = 1000;
|
||||
fmt.video.i_width = width;
|
||||
|
||||
if (sys->w != 0 && width > sys->w + sys->x)
|
||||
fmt.video.i_visible_width = sys->w;
|
||||
else
|
||||
fmt.video.i_visible_width = width - sys->x;
|
||||
|
||||
if (sys->h != 0 && height > sys->h + sys->y)
|
||||
fmt.video.i_visible_height = sys->h;
|
||||
else
|
||||
fmt.video.i_visible_height = height - sys->y;
|
||||
|
||||
fmt.video.i_height = fmt.video.i_visible_height;
|
||||
|
||||
sys->es = es_out_Add(demux->out, &fmt);
|
||||
sys->width = width;
|
||||
sys->height = height;
|
||||
(void) output;
|
||||
}
|
||||
|
||||
static void output_done_cb(void *data, struct wl_output *output)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
|
||||
(void) demux; (void) output;
|
||||
}
|
||||
|
||||
static void output_scale_cb(void *data, struct wl_output *output,
|
||||
int32_t factor)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
|
||||
(void) demux; (void) output; (void) factor;
|
||||
}
|
||||
|
||||
const struct wl_output_listener output_cbs =
|
||||
{
|
||||
output_geometry_cb,
|
||||
output_mode_cb,
|
||||
output_done_cb,
|
||||
output_scale_cb,
|
||||
};
|
||||
|
||||
static void screenshooter_done_cb(void *data,
|
||||
struct screenshooter *screenshooter)
|
||||
{
|
||||
bool *done = data;
|
||||
|
||||
*done = true;
|
||||
(void) screenshooter;
|
||||
}
|
||||
|
||||
const struct screenshooter_listener screenshooter_cbs =
|
||||
{
|
||||
screenshooter_done_cb,
|
||||
};
|
||||
|
||||
static block_t *Shoot(demux_t *demux)
|
||||
{
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
|
||||
char bufpath[] = "/tmp/"PACKAGE_NAME"XXXXXX";
|
||||
int fd = mkostemp(bufpath, O_CLOEXEC);
|
||||
if (fd == -1)
|
||||
{
|
||||
msg_Err(demux, "buffer creation error: %s", vlc_strerror_c(errno));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* NOTE: one extra line for overflow if screen-left > 0 */
|
||||
uint32_t pitch = 4u * sys->width;
|
||||
size_t size = (pitch * (sys->height + 1) + sys->pagemask) & ~sys->pagemask;
|
||||
block_t *block = NULL;
|
||||
|
||||
if (ftruncate(fd, size) < 0)
|
||||
{
|
||||
msg_Err(demux, "buffer allocation error: %s", vlc_strerror_c(errno));
|
||||
goto out;
|
||||
}
|
||||
|
||||
struct wl_shm_pool *pool = wl_shm_create_pool(sys->shm, fd, size);
|
||||
if (pool == NULL)
|
||||
goto out;
|
||||
|
||||
struct wl_buffer *buffer;
|
||||
buffer = wl_shm_pool_create_buffer(pool, 0, sys->width, sys->height,
|
||||
pitch, WL_SHM_FORMAT_XRGB8888);
|
||||
wl_shm_pool_destroy(pool);
|
||||
if (buffer == NULL)
|
||||
goto out;
|
||||
|
||||
sys->done = false;
|
||||
screenshooter_shoot(sys->screenshooter, sys->output, buffer);
|
||||
|
||||
while (!sys->done)
|
||||
wl_display_roundtrip(sys->display);
|
||||
|
||||
wl_buffer_destroy(buffer);
|
||||
block = block_File(fd);
|
||||
|
||||
if (block != NULL)
|
||||
{
|
||||
size_t skip = (sys->y * sys->width + sys->x) * 4;
|
||||
|
||||
block->p_buffer += skip;
|
||||
block->i_buffer -= skip;
|
||||
}
|
||||
|
||||
out:
|
||||
close(fd);
|
||||
return block;
|
||||
}
|
||||
|
||||
static void cleanup_wl_display_read(void *data)
|
||||
{
|
||||
struct wl_display *display = data;
|
||||
|
||||
wl_display_cancel_read(display);
|
||||
}
|
||||
|
||||
static void *Thread(void *data)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
struct wl_display *display = sys->display;
|
||||
struct pollfd ufd[1];
|
||||
unsigned interval = lroundf(CLOCK_FREQ / (sys->rate * 1000.f));
|
||||
|
||||
int canc = vlc_savecancel();
|
||||
vlc_cleanup_push(cleanup_wl_display_read, display);
|
||||
|
||||
ufd[0].fd = wl_display_get_fd(display);
|
||||
ufd[0].events = POLLIN;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (DisplayError(demux, display))
|
||||
break;
|
||||
|
||||
if (sys->es != NULL)
|
||||
{
|
||||
block_t *block = Shoot(demux);
|
||||
|
||||
block->i_pts = block->i_dts = mdate();
|
||||
es_out_Control(demux->out, ES_OUT_SET_PCR, block->i_pts);
|
||||
es_out_Send(demux->out, sys->es, block);
|
||||
}
|
||||
|
||||
while (wl_display_prepare_read(display) != 0)
|
||||
wl_display_dispatch_pending(display);
|
||||
wl_display_flush(display);
|
||||
vlc_restorecancel(canc);
|
||||
|
||||
while (poll(ufd, 1, interval) < 0);
|
||||
|
||||
canc = vlc_savecancel();
|
||||
wl_display_read_events(display);
|
||||
wl_display_dispatch_pending(display);
|
||||
}
|
||||
vlc_cleanup_pop();
|
||||
vlc_restorecancel(canc);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int Control(demux_t *demux, int query, va_list args)
|
||||
{
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
|
||||
switch (query)
|
||||
{
|
||||
case DEMUX_GET_POSITION:
|
||||
*va_arg(args, float *) = 0.f;
|
||||
break;
|
||||
|
||||
case DEMUX_GET_LENGTH:
|
||||
*va_arg(args, int64_t *) = 0;
|
||||
break;
|
||||
|
||||
case DEMUX_GET_TIME:
|
||||
*va_arg(args, int64_t *) = mdate() - sys->start;
|
||||
break;
|
||||
|
||||
case DEMUX_GET_FPS:
|
||||
*va_arg(args, float *) = sys->rate;
|
||||
break;
|
||||
|
||||
case DEMUX_CAN_PAUSE:
|
||||
*va_arg(args, bool *) = false; /* TODO */
|
||||
break;
|
||||
//case DEMUX_SET_PAUSE_STATE:
|
||||
// break;
|
||||
|
||||
case DEMUX_GET_PTS_DELAY:
|
||||
*va_arg(args, int64_t *) = INT64_C(1000)
|
||||
* var_InheritInteger(demux, "live-caching");
|
||||
break;
|
||||
|
||||
case DEMUX_CAN_CONTROL_PACE:
|
||||
case DEMUX_CAN_CONTROL_RATE:
|
||||
case DEMUX_CAN_SEEK:
|
||||
*va_arg(args, bool *) = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
|
||||
return VLC_SUCCESS;
|
||||
}
|
||||
|
||||
static void registry_global_cb(void *data, struct wl_registry *registry,
|
||||
uint32_t name, const char *iface, uint32_t vers)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
|
||||
msg_Dbg(demux, "global %3"PRIu32": %s version %"PRIu32, name, iface, vers);
|
||||
|
||||
if (!strcmp(iface, "wl_output"))
|
||||
sys->output = wl_registry_bind(registry, name, &wl_output_interface,
|
||||
1);
|
||||
else
|
||||
if (!strcmp(iface, "wl_shm"))
|
||||
sys->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
|
||||
else
|
||||
if (!strcmp(iface, "screenshooter"))
|
||||
sys->screenshooter = wl_registry_bind(registry, name,
|
||||
&screenshooter_interface, 1);
|
||||
}
|
||||
|
||||
static void registry_global_remove_cb(void *data, struct wl_registry *registry,
|
||||
uint32_t name)
|
||||
{
|
||||
demux_t *demux = data;
|
||||
|
||||
msg_Dbg(demux, "global remove %3"PRIu32, name);
|
||||
(void) registry;
|
||||
}
|
||||
|
||||
static const struct wl_registry_listener registry_cbs =
|
||||
{
|
||||
registry_global_cb,
|
||||
registry_global_remove_cb,
|
||||
};
|
||||
|
||||
static int Open(vlc_object_t *obj)
|
||||
{
|
||||
demux_t *demux = (demux_t *)obj;
|
||||
demux_sys_t *sys = malloc(sizeof (*sys));
|
||||
if (unlikely(sys == NULL))
|
||||
return VLC_ENOMEM;
|
||||
|
||||
/* Connect to the display server */
|
||||
char *dpy_name = var_InheritString(demux, "wl-display");
|
||||
sys->display = wl_display_connect(dpy_name);
|
||||
free(dpy_name);
|
||||
|
||||
if (sys->display == NULL)
|
||||
{
|
||||
free(sys);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
|
||||
sys->output = NULL;
|
||||
sys->shm = NULL;
|
||||
sys->screenshooter = NULL;
|
||||
sys->es = NULL;
|
||||
sys->pagemask = sysconf(_SC_PAGE_SIZE) - 1;
|
||||
sys->rate = var_InheritFloat(demux, "screen-fps");
|
||||
sys->x = var_InheritInteger(demux, "screen-left");
|
||||
sys->y = var_InheritInteger(demux, "screen-top");
|
||||
sys->w = var_InheritInteger(demux, "screen-width");
|
||||
sys->h = var_InheritInteger(demux, "screen-height");
|
||||
|
||||
if (1000.f * sys->rate <= 0x1.p-30)
|
||||
goto error;
|
||||
|
||||
demux->p_sys = sys;
|
||||
|
||||
/* Find the interesting singleton(s) */
|
||||
struct wl_registry *registry = wl_display_get_registry(sys->display);
|
||||
if (registry == NULL)
|
||||
goto error;
|
||||
|
||||
wl_registry_add_listener(registry, ®istry_cbs, demux);
|
||||
wl_display_roundtrip(sys->display);
|
||||
wl_registry_destroy(registry);
|
||||
|
||||
if (sys->output == NULL || sys->shm == NULL || sys->screenshooter == NULL)
|
||||
{
|
||||
msg_Err(demux, "screenshooter extension not supported");
|
||||
goto error;
|
||||
}
|
||||
|
||||
wl_output_add_listener(sys->output, &output_cbs, demux);
|
||||
screenshooter_add_listener(sys->screenshooter, &screenshooter_cbs,
|
||||
&sys->done);
|
||||
wl_display_roundtrip(sys->display);
|
||||
|
||||
if (DisplayError(demux, sys->display))
|
||||
goto error;
|
||||
|
||||
/* Initializes demux */
|
||||
sys->start = mdate();
|
||||
|
||||
if (vlc_clone(&sys->thread, Thread, demux, VLC_THREAD_PRIORITY_INPUT))
|
||||
goto error;
|
||||
|
||||
demux->pf_demux = NULL;
|
||||
demux->pf_control = Control;
|
||||
return VLC_SUCCESS;
|
||||
|
||||
error:
|
||||
if (sys->screenshooter != NULL)
|
||||
screenshooter_destroy(sys->screenshooter);
|
||||
if (sys->shm != NULL)
|
||||
wl_shm_destroy(sys->shm);
|
||||
if (sys->output != NULL)
|
||||
wl_output_destroy(sys->output);
|
||||
wl_display_disconnect(sys->display);
|
||||
free(sys);
|
||||
return VLC_EGENERIC;
|
||||
}
|
||||
|
||||
static void Close(vlc_object_t *obj)
|
||||
{
|
||||
demux_t *demux = (demux_t *)obj;
|
||||
demux_sys_t *sys = demux->p_sys;
|
||||
|
||||
vlc_cancel(sys->thread);
|
||||
vlc_join(sys->thread, NULL);
|
||||
|
||||
screenshooter_destroy(sys->screenshooter);
|
||||
wl_shm_destroy(sys->shm);
|
||||
wl_output_destroy(sys->output);
|
||||
wl_display_disconnect(sys->display);
|
||||
free(sys);
|
||||
}
|
||||
|
||||
#define FPS_TEXT N_("Frame rate")
|
||||
#define FPS_LONGTEXT N_( \
|
||||
"How many times the screen content should be refreshed per second.")
|
||||
|
||||
#define LEFT_TEXT N_("Region left column")
|
||||
#define LEFT_LONGTEXT N_( \
|
||||
"Abscissa of the capture region in pixels.")
|
||||
|
||||
#define TOP_TEXT N_("Region top row")
|
||||
#define TOP_LONGTEXT N_( \
|
||||
"Ordinate of the capture region in pixels.")
|
||||
|
||||
#define WIDTH_TEXT N_("Capture region width")
|
||||
#define WIDTH_LONGTEXT N_( \
|
||||
"Pixel width of the capture region, or 0 for full width")
|
||||
|
||||
#define HEIGHT_TEXT N_("Capture region height")
|
||||
#define HEIGHT_LONGTEXT N_( \
|
||||
"Pixel height of the capture region, or 0 for full height")
|
||||
|
||||
vlc_module_begin ()
|
||||
set_shortname (N_("Screen"))
|
||||
set_description (N_("Screen capture (with Wayland)"))
|
||||
set_category (CAT_INPUT)
|
||||
set_subcategory (SUBCAT_INPUT_ACCESS)
|
||||
set_capability ("access_demux", 0)
|
||||
set_callbacks (Open, Close)
|
||||
|
||||
/* XXX: VLC core does not support multiple configuration items with the
|
||||
* same name. So all default values and ranges must be the same as for XCB
|
||||
* for the time being. */
|
||||
add_float ("screen-fps", 2.0, FPS_TEXT, FPS_LONGTEXT, true)
|
||||
add_integer ("screen-left", 0, LEFT_TEXT, LEFT_LONGTEXT, true)
|
||||
change_integer_range (-32768, 32767)
|
||||
change_safe ()
|
||||
add_integer ("screen-top", 0, TOP_TEXT, TOP_LONGTEXT, true)
|
||||
change_integer_range (-32768, 32767)
|
||||
change_safe ()
|
||||
add_integer ("screen-width", 0, WIDTH_TEXT, WIDTH_LONGTEXT, true)
|
||||
change_integer_range (0, 65535)
|
||||
change_safe ()
|
||||
add_integer ("screen-height", 0, HEIGHT_TEXT, HEIGHT_LONGTEXT, true)
|
||||
change_integer_range (0, 65535)
|
||||
change_safe ()
|
||||
add_shortcut ("screen")
|
||||
vlc_module_end ()
|
|
@ -254,6 +254,7 @@ modules/access/rtsp/rtsp.h
|
|||
modules/access/screen/mac.c
|
||||
modules/access/screen/screen.c
|
||||
modules/access/screen/screen.h
|
||||
modules/access/screen/wayland.c
|
||||
modules/access/screen/win32.c
|
||||
modules/access/screen/xcb.c
|
||||
modules/access/sdp.c
|
||||
|
|
Loading…
Reference in New Issue