diff --git a/plugins/linux-xshm/CMakeLists.txt b/plugins/linux-xshm/CMakeLists.txt index dffac895c16e7b3b66d5ff470f2bbf3f4f797206..270e0541a7a0413c6fab418c3c5883a243a6ab7d 100644 --- a/plugins/linux-xshm/CMakeLists.txt +++ b/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) diff --git a/plugins/linux-xshm/README b/plugins/linux-xshm/README new file mode 100644 index 0000000000000000000000000000000000000000..3b6cf24d0fd354cf393c6d6d50f8893e32ea3d20 --- /dev/null +++ b/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 diff --git a/plugins/linux-xshm/xcursor.c b/plugins/linux-xshm/xcursor.c index a7448671c73392be891f5b5e55af505ec7ae1751..87b7d399cc71b4df1fdb39158283cdba8af04e4b 100644 --- a/plugins/linux-xshm/xcursor.c +++ b/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; +} + diff --git a/plugins/linux-xshm/xcursor.h b/plugins/linux-xshm/xcursor.h index 90846e17002991551a8134de195545fea8bd20fc..1fed9ce28f9a3e8e87d98d8abc8082dfa0836d23 100644 --- a/plugins/linux-xshm/xcursor.h +++ b/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 diff --git a/plugins/linux-xshm/xhelpers.c b/plugins/linux-xshm/xhelpers.c new file mode 100644 index 0000000000000000000000000000000000000000..efa4e638671fc7257f7a09797d4205af8f2de49d --- /dev/null +++ b/plugins/linux-xshm/xhelpers.c @@ -0,0 +1,150 @@ +/* +Copyright (C) 2014 by Leonhard Oelke + +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 . +*/ + +#include +#include +#include +#include + +#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); +} diff --git a/plugins/linux-xshm/xhelpers.h b/plugins/linux-xshm/xhelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..1c0cd0a3803634ea36960af4858fd3eb65680471 --- /dev/null +++ b/plugins/linux-xshm/xhelpers.h @@ -0,0 +1,101 @@ +/* +Copyright (C) 2014 by Leonhard Oelke + +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 . +*/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +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 diff --git a/plugins/linux-xshm/xshm-input.c b/plugins/linux-xshm/xshm-input.c index 7234a87484ea71e8a718f78434154e6dda725c8b..ea0d07f1bbf44033c12c217c541fe895ef35b8e4 100644 --- a/plugins/linux-xshm/xshm-input.c +++ b/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 . */ -#include -#include -#include +#include +#include +#include #include #include -#include #include #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 };