Просмотр исходного кода

Merge pull request #83 from fryshorts/xshm-input

linux-xshm: added support for multi-screen setups
Jim 11 лет назад
Родитель
Сommit
4d13e2fbca

+ 3 - 0
plugins/linux-xshm/CMakeLists.txt

@@ -7,10 +7,12 @@ include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/libobs")
 set(linux-xshm_SOURCES
 	linux-xshm.c
 	xcursor.c
+	xhelpers.c
 	xshm-input.c
 )
 set(linux-xshm_HEADERS
 	xcursor.h
+	xhelpers.h
 )
 
 add_library(linux-xshm MODULE
@@ -22,6 +24,7 @@ target_link_libraries(linux-xshm
 	${X11_LIBRARIES}
 	${X11_XShm_LIB}
 	${X11_Xfixes_LIB}
+	${X11_Xinerama_LIB}
 )
 
 install_obs_plugin(linux-xshm)

+ 19 - 0
plugins/linux-xshm/README

@@ -0,0 +1,19 @@
+
+Linux XShm capture plugin
+
+  This plugin uses the MIT-SHM extension for the X-server to capture the
+  desktop.
+
+Todo:
+
+ - handle resolution changes of screens
+ - handle adding/removing screens while recording
+ - support different depths
+
+Contributing:
+
+  If you are interested in helping out with the plugin, please drop by in the
+  #obs-dev channel on quakenet.
+
+References:
+ - http://www.x.org/releases/current/doc/xextproto/shm.html

+ 11 - 5
plugins/linux-xshm/xcursor.c

@@ -55,7 +55,7 @@ static void xcursor_create(xcursor_t *data, XFixesCursorImage *xc) {
 			texture_destroy(data->tex);
 
 		data->tex = gs_create_texture(xc->width, xc->height,
-			GS_RGBA, 1, (const void **) &pixels, GS_DYNAMIC);
+			GS_BGRA, 1, (const void **) &pixels, GS_DYNAMIC);
 	}
 
 	bfree(pixels);
@@ -66,8 +66,7 @@ static void xcursor_create(xcursor_t *data, XFixesCursorImage *xc) {
 }
 
 xcursor_t *xcursor_init(Display *dpy) {
-	xcursor_t *data = bmalloc(sizeof(xcursor_t));
-	memset(data, 0, sizeof(xcursor_t));
+	xcursor_t *data = bzalloc(sizeof(xcursor_t));
 
 	data->dpy = dpy;
 	xcursor_tick(data);
@@ -86,8 +85,8 @@ void xcursor_tick(xcursor_t *data) {
 
 	if (!data->tex || data->last_serial != xc->cursor_serial)
 		xcursor_create(data, xc);
-	data->pos_x = -1.0 * (xc->x - xc->xhot);
-	data->pos_y = -1.0 * (xc->y - xc->yhot);
+	data->pos_x = -1.0 * (xc->x - xc->xhot - data->x_org);
+	data->pos_y = -1.0 * (xc->y - xc->yhot - data->y_org);
 
 	XFree(xc);
 }
@@ -110,3 +109,10 @@ void xcursor_render(xcursor_t *data) {
 
 	gs_matrix_pop();
 }
+
+void xcursor_offset(xcursor_t* data, int_fast32_t x_org, int_fast32_t y_org)
+{
+	data->x_org = x_org;
+	data->y_org = y_org;
+}
+

+ 10 - 2
plugins/linux-xshm/xcursor.h

@@ -28,9 +28,12 @@ typedef struct {
 	float pos_x;
 	float pos_y;
 	unsigned long last_serial;
-	unsigned short int last_width;
-	unsigned short int last_height;
+	uint_fast32_t last_width;
+	uint_fast32_t last_height;
 	texture_t tex;
+
+	int_fast32_t x_org;
+	int_fast32_t y_org;
 } xcursor_t;
 
 /**
@@ -59,6 +62,11 @@ void xcursor_tick(xcursor_t *data);
  */
 void xcursor_render(xcursor_t *data);
 
+/**
+ * Specify offset for the cursor
+ */
+void xcursor_offset(xcursor_t *data, int_fast32_t x_org, int_fast32_t y_org);
+
 #ifdef __cplusplus
 }
 #endif

+ 150 - 0
plugins/linux-xshm/xhelpers.c

@@ -0,0 +1,150 @@
+/*
+Copyright (C) 2014 by Leonhard Oelke <[email protected]>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdint.h>
+#include <sys/shm.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/Xinerama.h>
+
+#include "xhelpers.h"
+
+int_fast32_t xinerama_is_active(Display *dpy)
+{
+	int minor, major;
+	if (!dpy)
+		return 0;
+	if (!XineramaQueryVersion(dpy, &minor, &major))
+		return 0;
+	if (!XineramaIsActive(dpy))
+		return 0;
+	return 1;
+}
+
+int_fast32_t xinerama_screen_count(Display *dpy)
+{
+	int screens;
+	if (!dpy)
+		return 0;
+	XFree(XineramaQueryScreens(dpy, &screens));
+	return screens;
+}
+
+int_fast32_t xinerama_screen_geo(Display *dpy, const int_fast32_t screen,
+	int_fast32_t *x, int_fast32_t *y, int_fast32_t *w, int_fast32_t *h)
+{
+	int screens;
+	XineramaScreenInfo *info;
+
+	if (!dpy)
+		goto fail;
+	info = XineramaQueryScreens(dpy, &screens);
+	if (screen < 0 || screen >= screens)
+		goto fail;
+
+	*x = info[screen].x_org;
+	*y = info[screen].y_org;
+	*w = info[screen].width;
+	*h = info[screen].height;
+
+	XFree(info);
+	return 0;
+fail:
+	if (info)
+		XFree(info);
+	
+	*x = *y = *w = *h = 0;
+	return -1;
+}
+
+int_fast32_t x11_screen_geo(Display *dpy, const int_fast32_t screen,
+	int_fast32_t *w, int_fast32_t *h)
+{
+	Screen *scr;
+
+	if (!dpy || screen < 0 || screen >= XScreenCount(dpy))
+		goto fail;
+
+	scr = XScreenOfDisplay(dpy, screen);
+	if (!scr)
+		goto fail;
+
+	*w = XWidthOfScreen(scr);
+	*h = XHeightOfScreen(scr);
+
+	return 0;
+fail:
+	*w = *h = 0;
+	return -1;
+}
+
+xshm_t *xshm_attach(Display *dpy, Screen *screen,
+	int_fast32_t w, int_fast32_t h)
+{
+	if (!dpy || !screen)
+		return NULL;
+
+	xshm_t *xshm = bzalloc(sizeof(xshm_t));
+
+	xshm->dpy = dpy;
+	xshm->image = XShmCreateImage(xshm->dpy, DefaultVisualOfScreen(screen),
+		DefaultDepthOfScreen(screen), ZPixmap, NULL, &xshm->info,
+		w, h);
+	if (!xshm->image)
+		goto fail;
+
+	xshm->info.shmid = shmget(IPC_PRIVATE,
+		xshm->image->bytes_per_line * xshm->image->height,
+		IPC_CREAT | 0700);
+	if (xshm->info.shmid < 0)
+		goto fail;
+
+	xshm->info.shmaddr
+		= xshm->image->data
+		= (char *) shmat(xshm->info.shmid, 0, 0);
+	if (xshm->info.shmaddr == (char *) -1)
+		goto fail;
+	xshm->info.readOnly = false;
+
+	if (!XShmAttach(xshm->dpy, &xshm->info))
+		goto fail;
+
+	xshm->attached = true;
+	return xshm;
+fail:
+	xshm_detach(xshm);
+	return NULL;
+}
+
+void xshm_detach(xshm_t *xshm)
+{
+	if (!xshm)
+		return;
+
+	if (xshm->attached)
+		XShmDetach(xshm->dpy, &xshm->info);
+
+	if (xshm->info.shmaddr != (char *) -1)
+		shmdt(xshm->info.shmaddr);
+
+	if (xshm->info.shmid != -1)
+		shmctl(xshm->info.shmid, IPC_RMID, NULL);
+
+	if (xshm->image)
+		XDestroyImage(xshm->image);
+
+	bfree(xshm);
+}

+ 101 - 0
plugins/linux-xshm/xhelpers.h

@@ -0,0 +1,101 @@
+/*
+Copyright (C) 2014 by Leonhard Oelke <[email protected]>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 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 General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/extensions/XShm.h>
+#include <obs.h>
+
+typedef struct {
+	XShmSegmentInfo info;
+	XImage *image;
+	Display *dpy;
+	bool attached;
+} xshm_t;
+
+/**
+ * Check for Xinerama extension
+ *
+ * @return > 0 if Xinerama is available and active
+ */
+int_fast32_t xinerama_is_active(Display *dpy);
+
+/**
+ * Get the number of Xinerama screens
+ *
+ * @return number of screens
+ */
+int_fast32_t xinerama_screen_count(Display *dpy);
+
+/**
+ * Get screen geometry for a Xinerama screen
+ *
+ * @note On error the passed coordinates/sizes will be set to 0.
+ *
+ * @param dpy X11 display
+ * @param screen screen number to get geometry for
+ * @param x x-coordinate of the screen
+ * @param y y-coordinate of the screen
+ * @param w width of the screen
+ * @param h height of the screen
+ *
+ * @return < 0 on error
+ */
+int_fast32_t xinerama_screen_geo(Display *dpy, const int_fast32_t screen,
+	int_fast32_t *x, int_fast32_t *y, int_fast32_t *w, int_fast32_t *h);
+
+/**
+ * Get screen geometry for a X11 screen
+ *
+ * @note On error the passed sizes will be set to 0.
+ *
+ * @param dpy X11 display
+ * @param screen screen number to get geometry for
+ * @param w width of the screen
+ * @param h height of the screen
+ *
+ * @return < 0 on error
+ */
+int_fast32_t x11_screen_geo(Display *dpy, const int_fast32_t screen,
+	int_fast32_t *w, int_fast32_t *h);
+
+/**
+ * Attach a shared memory segment to the X-Server
+ *
+ * @param dpy X11 Display
+ * @param screen X11 Screen
+ * @param w width for the shared memory segment
+ * @param h height for the shared memory segment
+ *
+ * @return NULL on error
+ */
+xshm_t *xshm_attach(Display *dpy, Screen *screen,
+	int_fast32_t w, int_fast32_t h);
+
+/**
+ * Detach a shared memory segment
+ */
+void xshm_detach(xshm_t *xshm);
+
+#ifdef __cplusplus
+}
+#endif

+ 197 - 92
plugins/linux-xshm/xshm-input.c

@@ -14,193 +14,298 @@ GNU General Public License for more details.
 You should have received a copy of the GNU General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
-#include <stdlib.h>
-#include <stdio.h>
 
-#include <sys/shm.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
 #include <X11/Xlib.h>
 #include <X11/Xutil.h>
-#include <X11/extensions/XShm.h>
 
 #include <obs.h>
 #include "xcursor.h"
+#include "xhelpers.h"
 
 #define XSHM_DATA(voidptr) struct xshm_data *data = voidptr;
 
 struct xshm_data {
 	Display *dpy;
-	Window root_window;
-	uint32_t width, height;
-	int shm_attached;
-	XShmSegmentInfo shm_info;
-	XImage *image;
+	Screen *screen;
+
+	int_fast32_t x_org, y_org;
+	int_fast32_t width, height;
+
+	xshm_t *xshm;
 	texture_t texture;
+
+	bool show_cursor;
 	xcursor_t *cursor;
+
+	bool use_xinerama;
 };
 
+/**
+ * Resize the texture
+ *
+ * This will automatically create the texture if it does not exist
+ */
+static void xshm_resize_texture(struct xshm_data *data)
+{
+	gs_entercontext(obs_graphics());
+
+	if (data->texture)
+		texture_destroy(data->texture);
+	data->texture = gs_create_texture(data->width, data->height,
+		GS_BGRA, 1, NULL, GS_DYNAMIC);
+
+	gs_leavecontext();
+}
+
+/**
+ * Update the capture
+ *
+ * @return < 0 on error, 0 when size is unchanged, > 1 on size change
+ */
+static int_fast32_t xshm_update_geometry(struct xshm_data *data,
+	obs_data_t settings)
+{
+	int_fast32_t old_width = data->width;
+	int_fast32_t old_height = data->height;
+	int_fast32_t screen = obs_data_getint(settings, "screen");
+
+	if (data->use_xinerama) {
+		if (xinerama_screen_geo(data->dpy, screen,
+			&data->x_org, &data->y_org,
+			&data->width, &data->height) < 0) {
+			return -1;
+		}
+		data->screen = XDefaultScreenOfDisplay(data->dpy);
+	}
+	else {
+		data->x_org = 0;
+		data->y_org = 0;
+		if (x11_screen_geo(data->dpy, screen,
+			&data->width, &data->height) < 0) {
+			return -1;
+		}
+		data->screen = XScreenOfDisplay(data->dpy, screen);
+	}
+
+	if (!data->width || !data->height) {
+		blog(LOG_ERROR, "xshm-input: Failed to get geometry");
+		return -1;
+	}
+
+	blog(LOG_INFO, "xshm-input: Geometry %"PRIdFAST32"x%"PRIdFAST32
+		" @ %"PRIdFAST32",%"PRIdFAST32,
+		data->width, data->height, data->x_org, data->y_org);
+
+	if (old_width == data->width && old_height == data->height)
+		return 0;
+
+	return 1;
+}
+
+/**
+ * Returns the name of the plugin
+ */
 static const char* xshm_getname(const char* locale)
 {
 	UNUSED_PARAMETER(locale);
 	return "X11 Shared Memory Screen Input";
 }
 
-static void xshm_destroy(void *vptr)
+/**
+ * Update the capture with changed settings
+ */
+static void xshm_update(void *vptr, obs_data_t settings)
 {
 	XSHM_DATA(vptr);
 
-	if (!data)
-		return;
+	data->show_cursor = obs_data_getbool(settings, "show_cursor");
 
-	gs_entercontext(obs_graphics());
+	if (data->xshm)
+		xshm_detach(data->xshm);
 
-	texture_destroy(data->texture);
-	xcursor_destroy(data->cursor);
-
-	gs_leavecontext();
+	if (xshm_update_geometry(data, settings) < 0) {
+		blog(LOG_ERROR, "xshm-input: failed to update geometry !");
+		return;
+	}
 
-	if (data->shm_attached)
-		XShmDetach(data->dpy, &data->shm_info);
+	xshm_resize_texture(data);
+	xcursor_offset(data->cursor, data->x_org, data->y_org);
 
-	if (data->shm_info.shmaddr != (char *) -1) {
-		shmdt(data->shm_info.shmaddr);
-		data->shm_info.shmaddr = (char *) -1;
+	data->xshm = xshm_attach(data->dpy, data->screen,
+		data->width, data->height);
+	if (!data->xshm) {
+		blog(LOG_ERROR, "xshm-input: failed to attach shm !");
+		return;
 	}
+}
+
+/**
+ * Get the default settings for the capture
+ */
+static void xshm_defaults(obs_data_t defaults)
+{
+	obs_data_set_default_int(defaults, "screen", 0);
+	obs_data_setbool(defaults, "show_cursor", true);
+}
 
-	if (data->shm_info.shmid != -1)
-		shmctl(data->shm_info.shmid, IPC_RMID, NULL);
+/**
+ * Get the properties for the capture
+ */
+static obs_properties_t xshm_properties(const char *locale)
+{
+	obs_properties_t props = obs_properties_create(locale);
+	int_fast32_t screen_max;
 
-	if (data->image)
-		XDestroyImage(data->image);
+	Display *dpy = XOpenDisplay(NULL);
+	screen_max = xinerama_is_active(dpy)
+		? xinerama_screen_count(dpy)
+		: XScreenCount(dpy);
+	screen_max = (screen_max) ? screen_max - 1 : 0;
+	XCloseDisplay(dpy);
 
-	if (data->dpy)
-		XCloseDisplay(data->dpy);
+	obs_properties_add_int(props, "screen", "Screen", 0, screen_max, 1);
+	obs_properties_add_bool(props, "show_cursor", "Capture Cursor");
 
-	bfree(data);
+	return props;
 }
 
-static void *xshm_create(obs_data_t settings, obs_source_t source)
+/**
+ * Destroy the capture
+ */
+static void xshm_destroy(void *vptr)
 {
-	UNUSED_PARAMETER(settings);
-	UNUSED_PARAMETER(source);
-
+	XSHM_DATA(vptr);
 
-	struct xshm_data *data = bmalloc(sizeof(struct xshm_data));
-	memset(data, 0, sizeof(struct xshm_data));
+	if (!data)
+		return;
 
-	data->dpy = XOpenDisplay(NULL);
-	if (!data->dpy)
-		goto fail;
+	gs_entercontext(obs_graphics());
 
-	Screen *screen = XDefaultScreenOfDisplay(data->dpy);
-	data->width = WidthOfScreen(screen);
-	data->height = HeightOfScreen(screen);
-	data->root_window = XRootWindowOfScreen(screen);
-	Visual *visual = DefaultVisualOfScreen(screen);
-	int depth = DefaultDepthOfScreen(screen);
+	if (data->texture)
+		texture_destroy(data->texture);
+	if (data->cursor)
+		xcursor_destroy(data->cursor);
 
-	if (!XShmQueryExtension(data->dpy))
-		goto fail;
+	gs_leavecontext();
 
-	data->image = XShmCreateImage(data->dpy, visual, depth,
-		ZPixmap, NULL, &data->shm_info, data->width, data->height);
-	if (!data->image)
-		goto fail;
+	if (data->xshm)
+		xshm_detach(data->xshm);
+	if (data->dpy)
+		XCloseDisplay(data->dpy);
 
-	data->shm_info.shmid = shmget(IPC_PRIVATE,
-		data->image->bytes_per_line * data->image->height,
-		IPC_CREAT | 0700);
-	if (data->shm_info.shmid < 0)
-		goto fail;
+	bfree(data);
+}
 
-	data->shm_info.shmaddr
-		= data->image->data
-		= (char *) shmat(data->shm_info.shmid, 0, 0);
-	if (data->shm_info.shmaddr == (char *) -1)
-		goto fail;
-	data->shm_info.readOnly = False;
+/**
+ * Create the capture
+ */
+static void *xshm_create(obs_data_t settings, obs_source_t source)
+{
+	UNUSED_PARAMETER(source);
 
+	struct xshm_data *data = bzalloc(sizeof(struct xshm_data));
 
-	if (!XShmAttach(data->dpy, &data->shm_info))
+	data->dpy = XOpenDisplay(NULL);
+	if (!data->dpy) {
+		blog(LOG_ERROR, "xshm-input: Unable to open X display !");
 		goto fail;
-	data->shm_attached = 1;
+	}
 
-	if (!XShmGetImage(data->dpy, data->root_window, data->image,
-		0, 0, AllPlanes)) {
+	if (!XShmQueryExtension(data->dpy)) {
+		blog(LOG_ERROR, "xshm-input: XShm extension not found !");
 		goto fail;
 	}
 
+	data->use_xinerama = xinerama_is_active(data->dpy) ? true : false;
 
 	gs_entercontext(obs_graphics());
-	data->texture = gs_create_texture(data->width, data->height,
-		GS_BGRA, 1, (const void**) &data->image->data, GS_DYNAMIC);
 	data->cursor = xcursor_init(data->dpy);
 	gs_leavecontext();
 
-	if (!data->texture)
-		goto fail;
+	xshm_update(data, settings);
 
 	return data;
-
 fail:
 	xshm_destroy(data);
 	return NULL;
 }
 
+/**
+ * Prepare the capture data
+ */
 static void xshm_video_tick(void *vptr, float seconds)
 {
 	UNUSED_PARAMETER(seconds);
 	XSHM_DATA(vptr);
 
-	gs_entercontext(obs_graphics());
+	if (!data->xshm)
+		return;
 
+	gs_entercontext(obs_graphics());
 
-	XShmGetImage(data->dpy, data->root_window, data->image,
-		0, 0, AllPlanes);
-	texture_setimage(data->texture, (void *) data->image->data,
-		data->width * 4, False);
+	XShmGetImage(data->dpy, XRootWindowOfScreen(data->screen),
+		data->xshm->image, data->x_org, data->y_org, AllPlanes);
+	texture_setimage(data->texture, (void *) data->xshm->image->data,
+		data->width * 4, false);
 
 	xcursor_tick(data->cursor);
 
 	gs_leavecontext();
 }
 
+/**
+ * Render the capture data
+ */
 static void xshm_video_render(void *vptr, effect_t effect)
 {
 	XSHM_DATA(vptr);
 
+	if (!data->xshm)
+		return;
+
 	eparam_t image = effect_getparambyname(effect, "image");
 	effect_settexture(effect, image, data->texture);
 
-	gs_enable_blending(False);
-
+	gs_enable_blending(false);
 	gs_draw_sprite(data->texture, 0, 0, 0);
 
-	xcursor_render(data->cursor);
+	if (data->show_cursor)
+		xcursor_render(data->cursor);
 }
 
+/**
+ * Width of the captured data
+ */
 static uint32_t xshm_getwidth(void *vptr)
 {
 	XSHM_DATA(vptr);
-
-	return texture_getwidth(data->texture);
+	return data->width;
 }
 
+/**
+ * Height of the captured data
+ */
 static uint32_t xshm_getheight(void *vptr)
 {
 	XSHM_DATA(vptr);
-
-	return texture_getheight(data->texture);
+	return data->height;
 }
 
 struct obs_source_info xshm_input = {
-    .id           = "xshm_input",
-    .type         = OBS_SOURCE_TYPE_INPUT,
-    .output_flags = OBS_SOURCE_VIDEO,
-    .getname      = xshm_getname,
-    .create       = xshm_create,
-    .destroy      = xshm_destroy,
-    .video_tick   = xshm_video_tick,
-    .video_render = xshm_video_render,
-    .getwidth     = xshm_getwidth,
-    .getheight    = xshm_getheight
+	.id           = "xshm_input",
+	.type         = OBS_SOURCE_TYPE_INPUT,
+	.output_flags = OBS_SOURCE_VIDEO,
+	.getname      = xshm_getname,
+	.create       = xshm_create,
+	.destroy      = xshm_destroy,
+	.update       = xshm_update,
+	.defaults     = xshm_defaults,
+	.properties   = xshm_properties,
+	.video_tick   = xshm_video_tick,
+	.video_render = xshm_video_render,
+	.getwidth     = xshm_getwidth,
+	.getheight    = xshm_getheight
 };