diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 754040d3a..df63cefc1 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -444,6 +444,7 @@ void OBSBasic::OBSInit() obs_load_module("win-capture"); #else obs_load_module("linux-xshm"); + obs_load_module("linux-xcomposite"); obs_load_module("linux-pulseaudio"); #endif diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6d0c5f6c1..56b474076 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -9,6 +9,7 @@ elseif(APPLE) add_subdirectory(mac-capture) elseif("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") add_subdirectory(linux-xshm) + add_subdirectory(linux-xcomposite) add_subdirectory(linux-pulseaudio) endif() diff --git a/plugins/linux-xcomposite/CMakeLists.txt b/plugins/linux-xcomposite/CMakeLists.txt new file mode 100644 index 000000000..2eca7286d --- /dev/null +++ b/plugins/linux-xcomposite/CMakeLists.txt @@ -0,0 +1,28 @@ +project(linux-xcomposite) + +find_package(X11) +if(NOT X11_Xcomposite_FOUND) + message(STATUS "Xcomposite library not found, Xcomposite plugin disabled") + return() +endif() + +include_directories( + ${X11_Xcomposite_INCLUDE_PATH} + ${X11_X11_INCLUDE_PATH}) + +set(linux-xcomposite_SOURCES + plugin-main.cpp + xcompcap-main.cpp + xcompcap-main.h + xcompcap-helper.cpp + xcompcap-helper.h) + +add_library(linux-xcomposite MODULE + ${linux-xcomposite_SOURCES}) +target_link_libraries(linux-xcomposite + libobs + glad + ${X11_X11_LIB} + ${X11_Xcomposite_LIB}) + +install_obs_plugin(linux-xcomposite) diff --git a/plugins/linux-xcomposite/plugin-main.cpp b/plugins/linux-xcomposite/plugin-main.cpp new file mode 100644 index 000000000..cd814852b --- /dev/null +++ b/plugins/linux-xcomposite/plugin-main.cpp @@ -0,0 +1,101 @@ +#include + +#include "xcompcap-main.h" + +static void* xcompcap_create(obs_data_t settings, obs_source_t source) +{ + return new XCompcapMain(settings, source); +} + +static void xcompcap_destroy(void *data) +{ + XCompcapMain* cc = (XCompcapMain*)data; + delete cc; +} + +static void xcompcap_video_tick(void* data, float seconds) +{ + XCompcapMain* cc = (XCompcapMain*)data; + cc->tick(seconds); +} + +static void xcompcap_video_render(void* data, effect_t effect) +{ + XCompcapMain* cc = (XCompcapMain*)data; + cc->render(effect); +} + +static uint32_t xcompcap_getwidth(void* data) +{ + XCompcapMain* cc = (XCompcapMain*)data; + return cc->width(); +} + +static uint32_t xcompcap_getheight(void* data) +{ + XCompcapMain* cc = (XCompcapMain*)data; + return cc->height(); +} + +static obs_properties_t xcompcap_props(const char *locale) +{ + return XCompcapMain::properties(locale); +} + +void xcompcap_defaults(obs_data_t settings) +{ + XCompcapMain::defaults(settings); +} + +void xcompcap_update(void *data, obs_data_t settings) +{ + XCompcapMain* cc = (XCompcapMain*)data; + cc->updateSettings(settings); +} + +OBS_DECLARE_MODULE() + +static const char* xcompcap_getname(const char* locale) +{ + UNUSED_PARAMETER(locale); + + return "Xcomposite capture"; +} + +bool obs_module_load(uint32_t libobs_version) +{ + UNUSED_PARAMETER(libobs_version); + + if (!XCompcapMain::init()) + return false; + + obs_source_info sinfo; + memset(&sinfo, 0, sizeof(obs_source_info)); + + sinfo.id = "xcomposite_input"; + sinfo.output_flags = OBS_SOURCE_VIDEO; + + sinfo.getname = xcompcap_getname; + sinfo.create = xcompcap_create; + sinfo.destroy = xcompcap_destroy; + sinfo.properties = xcompcap_props; + sinfo.defaults = xcompcap_defaults; + sinfo.update = xcompcap_update; + sinfo.video_tick = xcompcap_video_tick; + sinfo.video_render = xcompcap_video_render; + sinfo.getwidth = xcompcap_getwidth; + sinfo.getheight = xcompcap_getheight; + + obs_register_source(&sinfo); + + blog(LOG_INFO, "Xcomposite capture plugin loaded"); + + return true; +} + +void obs_module_unload() +{ + XCompcapMain::deinit(); + + blog(LOG_INFO, "Xcomposite capture plugin unloaded"); +} diff --git a/plugins/linux-xcomposite/xcompcap-helper.cpp b/plugins/linux-xcomposite/xcompcap-helper.cpp new file mode 100644 index 000000000..ef24baa06 --- /dev/null +++ b/plugins/linux-xcomposite/xcompcap-helper.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include + +#include +#include + +#include + +#include "xcompcap-helper.h" + +namespace XCompcap +{ + static Display* xdisplay = 0; + + Display *disp() + { + if (!xdisplay) + xdisplay = XOpenDisplay(NULL); + + return xdisplay; + } + + void cleanupDisplay() + { + if (!xdisplay) + return; + + XCloseDisplay(xdisplay); + xdisplay = 0; + } + + static void getAllWindows(Window parent, std::list& windows) + { + UNUSED_PARAMETER(parent); + UNUSED_PARAMETER(windows); + } + + std::list getAllWindows() + { + std::list res; + + for (int i = 0; i < ScreenCount(disp()); ++i) + getAllWindows(RootWindow(disp(), i), res); + + return res; + } + + std::list getTopLevelWindows() + { + std::list res; + + Atom netClList = XInternAtom(disp(), "_NET_CLIENT_LIST", true); + Atom actualType; + int format; + unsigned long num, bytes; + Window* data = 0; + + for (int i = 0; i < ScreenCount(disp()); ++i) { + Window rootWin = RootWindow(disp(), i); + + int status = XGetWindowProperty( + disp(), + rootWin, + netClList, + 0L, + ~0L, + false, + AnyPropertyType, + &actualType, + &format, + &num, + &bytes, + (uint8_t**)&data); + + if (status != Success) { + blog(LOG_WARNING, "Failed getting root " + "window properties"); + continue; + } + + for (unsigned long i = 0; i < num; ++i) + res.push_back(data[i]); + + XFree(data); + } + + return res; + } + + int getRootWindowScreen(Window root) + { + XWindowAttributes attr; + + if (!XGetWindowAttributes(disp(), root, &attr)) + return DefaultScreen(disp()); + + return XScreenNumberOfScreen(attr.screen); + } + + std::string getWindowName(Window win) + { + Atom netWmName = XInternAtom(disp(), "_NET_WM_NAME", false); + int n; + char **list = 0; + XTextProperty tp; + std::string res = "unknown"; + + XGetTextProperty(disp(), win, &tp, netWmName); + + if (!tp.nitems) + XGetWMName(disp(), win, &tp); + + if (!tp.nitems) + return "error"; + + if (tp.encoding == XA_STRING) { + res = (char*)tp.value; + } else { + int ret = XmbTextPropertyToTextList(disp(), &tp, &list, + &n); + + if (ret >= Success && n > 0 && *list) { + res = *list; + XFreeStringList(list); + } + } + + XFree(tp.value); + + return res; + } + + std::string getWindowCommand(Window win) + { + Atom xi = XInternAtom(disp(), "WM_COMMAND", false); + int n; + char **list = 0; + XTextProperty tp; + std::string res = "error"; + + XGetTextProperty(disp(), win, &tp, xi); + + if (!tp.nitems) + return std::string(); + + if (tp.encoding == XA_STRING) { + res = (char*)tp.value; + } else { + int ret = XmbTextPropertyToTextList(disp(), &tp, &list, + &n); + if (ret >= Success && n > 0 && *list) { + res = *list; + XFreeStringList(list); + } + } + + XFree(tp.value); + + return res; + } + + int getWindowPid(Window win) + { + UNUSED_PARAMETER(win); + return 1234; //TODO + } + + static std::unordered_set changedWindows; + static pthread_mutex_t changeLock = PTHREAD_MUTEX_INITIALIZER; + void processEvents() + { + PLock lock(&changeLock); + + XLockDisplay(disp()); + + while (XEventsQueued(disp(), QueuedAfterReading) > 0) { + XEvent ev; + + XNextEvent(disp(), &ev); + + if (ev.type == ConfigureNotify) + changedWindows.insert(ev.xconfigure.event); + + if (ev.type == MapNotify) + changedWindows.insert(ev.xmap.event); + + if (ev.type == DestroyNotify) + changedWindows.insert(ev.xdestroywindow.event); + } + + XUnlockDisplay(disp()); + } + + bool windowWasReconfigured(Window win) + { + PLock lock(&changeLock); + + auto it = changedWindows.find(win); + + if (it != changedWindows.end()) { + changedWindows.erase(it); + return true; + } + + return false; + } + +} + + +PLock::PLock(pthread_mutex_t* mtx, bool trylock) + :m(mtx) +{ + if (trylock) + islock = mtx && pthread_mutex_trylock(mtx) == 0; + else + islock = mtx && pthread_mutex_lock(mtx) == 0; +} + +PLock::~PLock() +{ + if (islock) { + pthread_mutex_unlock(m); + } +} + +bool PLock::isLocked() +{ + return islock; +} + +void PLock::unlock() +{ + if (islock) { + pthread_mutex_unlock(m); + islock = false; + } +} + +void PLock::lock() +{ + if (!islock) { + pthread_mutex_lock(m); + islock = true; + } +} + + + +static bool* curErrorTarget = 0; +static char curErrorText[200]; +static int xerrorlock_handler(Display* disp, XErrorEvent* err) +{ + + if (curErrorTarget) + *curErrorTarget = true; + + XGetErrorText(disp, err->error_code, curErrorText, 200); + + return 0; +} + +XErrorLock::XErrorLock() +{ + goterr = false; + islock = false; + prevhandler = 0; + + lock(); +} + +XErrorLock::~XErrorLock() +{ + unlock(); +} + +bool XErrorLock::isLocked() +{ + return islock; +} + +void XErrorLock::lock() +{ + if (!islock) { + XLockDisplay(XCompcap::disp()); + XSync(XCompcap::disp(), 0); + + curErrorTarget = &goterr; + curErrorText[0] = 0; + prevhandler = XSetErrorHandler(xerrorlock_handler); + + islock = true; + } +} + +void XErrorLock::unlock() +{ + if (islock) { + curErrorTarget = 0; + XSetErrorHandler(prevhandler); + prevhandler = 0; + XUnlockDisplay(XCompcap::disp()); + islock = false; + } +} + +bool XErrorLock::gotError() +{ + if (!islock) + return false; + + XSync(XCompcap::disp(), 0); + + bool res = goterr; + goterr = false; + return res; +} + +std::string XErrorLock::getErrorText() +{ + return curErrorText; +} + +void XErrorLock::resetError() +{ + if (islock) + XSync(XCompcap::disp(), 0); + + goterr = false; + curErrorText[0] = 0; +} + + +ObsGsContextHolder::ObsGsContextHolder() +{ + gs_entercontext(obs_graphics()); +} + +ObsGsContextHolder::~ObsGsContextHolder() +{ + gs_leavecontext(); +} diff --git a/plugins/linux-xcomposite/xcompcap-helper.h b/plugins/linux-xcomposite/xcompcap-helper.h new file mode 100644 index 000000000..d4b008f43 --- /dev/null +++ b/plugins/linux-xcomposite/xcompcap-helper.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#define blog(level, msg, ...) blog(level, "xcompcap: " msg, ##__VA_ARGS__) + + +class PLock +{ + pthread_mutex_t *m; + bool islock; + + public: + PLock(const PLock&) = delete; + PLock& operator=(const PLock&) = delete; + + PLock(pthread_mutex_t* mtx, bool trylock = false); + + ~PLock(); + + bool isLocked(); + + void unlock(); + void lock(); +}; + +class XErrorLock +{ + bool islock; + bool goterr; + XErrorHandler prevhandler; + + public: + XErrorLock(const XErrorLock&) = delete; + XErrorLock& operator=(const XErrorLock&) = delete; + + XErrorLock(); + ~XErrorLock(); + + bool isLocked(); + + void unlock(); + void lock(); + + bool gotError(); + std::string getErrorText(); + void resetError(); +}; + +class ObsGsContextHolder +{ + public: + ObsGsContextHolder(const ObsGsContextHolder&) = delete; + ObsGsContextHolder& operator=(const ObsGsContextHolder&) = delete; + + ObsGsContextHolder(); + ~ObsGsContextHolder(); +}; + +namespace XCompcap +{ + Display* disp(); + void cleanupDisplay(); + + std::string getWindowCommand(Window win); + int getRootWindowScreen(Window root); + std::string getWindowName(Window win); + int getWindowPid(Window win); + std::list getTopLevelWindows(); + std::list getAllWindows(); + + void processEvents(); + bool windowWasReconfigured(Window win); +} diff --git a/plugins/linux-xcomposite/xcompcap-main.cpp b/plugins/linux-xcomposite/xcompcap-main.cpp new file mode 100644 index 000000000..bd4b42b42 --- /dev/null +++ b/plugins/linux-xcomposite/xcompcap-main.cpp @@ -0,0 +1,455 @@ +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "xcompcap-main.h" +#include "xcompcap-helper.h" + +#define xdisp (XCompcap::disp()) +#define WIN_STRING_DIV "\r\n" + +bool XCompcapMain::init() +{ + if (!xdisp) { + blog(LOG_ERROR, "failed opening display"); + return false; + } + + int eventBase, errorBase; + if (!XCompositeQueryExtension(xdisp, &eventBase, &errorBase)) { + blog(LOG_ERROR, "Xcomposite extension not supported"); + return false; + } + + int major = 0, minor = 2; + XCompositeQueryVersion(xdisp, &major, &minor); + + if (major == 0 && minor < 2) { + blog(LOG_ERROR, "Xcomposite extension is too old: %d.%d < 0.2", + major, minor); + return false; + } + + return true; +} + +void XCompcapMain::deinit() +{ + XCompcap::cleanupDisplay(); +} + +obs_properties_t XCompcapMain::properties(const char *locale) +{ + obs_properties_t props = obs_properties_create(locale); + + obs_property_t wins = obs_properties_add_list(props, "capture_window", + "Captured Window", OBS_COMBO_TYPE_LIST, + OBS_COMBO_FORMAT_STRING); + + for (Window win: XCompcap::getTopLevelWindows()) { + std::string wname = XCompcap::getWindowName(win); + std::string progpath = XCompcap::getWindowCommand(win); + std::string winid = std::to_string((long long)win); + std::string desc = + (winid + WIN_STRING_DIV + wname + + WIN_STRING_DIV + progpath); + + obs_property_list_add_string(wins, wname.c_str(), + desc.c_str()); + } + + obs_properties_add_int(props, "cut_top", "Cut top pixels", + 0, 4096, 1); + obs_properties_add_int(props, "cut_left", "Cut left pixels", + 0, 4096, 1); + obs_properties_add_int(props, "cut_right", "Cut right pixels", + 0, 4096, 1); + obs_properties_add_int(props, "cut_bot", "Cut bottom pixels", + 0, 4096, 1); + + obs_properties_add_bool(props, "swap_redblue", "Swap red and blue"); + obs_properties_add_bool(props, "lock_x", "Lock X server when " + "capturing"); + + return props; +} + +void XCompcapMain::defaults(obs_data_t settings) +{ + obs_data_setstring(settings, "capture_window", ""); + obs_data_setint(settings, "cut_top", 0); + obs_data_setint(settings, "cut_left", 0); + obs_data_setint(settings, "cut_right", 0); + obs_data_setint(settings, "cut_bot", 0); + obs_data_setbool(settings, "swap_redblue", false); + obs_data_setbool(settings, "lock_x", false); +} + + +struct XCompcapMain_private +{ + XCompcapMain_private() + :win(0) + ,cut_top(0), cur_cut_top(0) + ,cut_left(0), cur_cut_left(0) + ,cut_right(0), cur_cut_right(0) + ,cut_bot(0), cur_cut_bot(0) + ,inverted(false) + ,width(0),height(0) + ,pixmap(0) + ,glxpixmap(0) + ,tex(0) + ,gltex(0) + { + pthread_mutexattr_init(&lockattr); + pthread_mutexattr_settype(&lockattr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&lock, &lockattr); + } + + ~XCompcapMain_private() + { + pthread_mutex_destroy(&lock); + pthread_mutexattr_destroy(&lockattr); + } + + obs_source_t source; + + Window win; + int cut_top, cur_cut_top; + int cut_left, cur_cut_left; + int cut_right, cur_cut_right; + int cut_bot, cur_cut_bot; + bool inverted; + bool swapRedBlue; + bool lockX; + + uint32_t width; + uint32_t height; + + Pixmap pixmap; + GLXPixmap glxpixmap; + texture_t tex; + texture_t gltex; + + pthread_mutex_t lock; + pthread_mutexattr_t lockattr; +}; + + +XCompcapMain::XCompcapMain(obs_data_t settings, obs_source_t source) +{ + p = new XCompcapMain_private; + p->source = source; + + updateSettings(settings); +} + +static void xcc_cleanup(XCompcapMain_private *p); + +XCompcapMain::~XCompcapMain() +{ + ObsGsContextHolder obsctx; + + if (p->tex) { + texture_destroy(p->tex); + p->tex = 0; + } + + xcc_cleanup(p); + + delete p; +} + +static Window getWindowFromString(std::string wstr) +{ + if (wstr == "") { + return XCompcap::getTopLevelWindows().front(); + } + + if (wstr.substr(0, 4) == "root") { + int i = std::stoi("0" + wstr.substr(4)); + return RootWindow(xdisp, i); + } + + size_t firstMark = wstr.find(WIN_STRING_DIV); + + if (firstMark == std::string::npos) + return (Window)std::stol(wstr); + + std::string widstr = wstr.substr(0, firstMark); + Window wid = (Window)std::stol(widstr); + + wstr = wstr.substr(firstMark + strlen(WIN_STRING_DIV)); + + size_t lastMark = wstr.rfind(WIN_STRING_DIV); + std::string wname = wstr.substr(0, lastMark); + + Window matchedNameWin = wid; + for (Window cwin: XCompcap::getTopLevelWindows()) { + std::string cwinname = XCompcap::getWindowName(cwin); + + if (cwin == wid && wname == cwinname) + return wid; + + if (wname == cwinname) + matchedNameWin = cwin; + } + + return matchedNameWin; +} + +static void xcc_cleanup(XCompcapMain_private *p) +{ + if (p->gltex) { + texture_destroy(p->gltex); + p->gltex = 0; + } + + if (p->glxpixmap) { + glXDestroyPixmap(xdisp, p->glxpixmap); + p->glxpixmap = 0; + } + + if (p->pixmap) { + XFreePixmap(xdisp, p->pixmap); + p->pixmap = 0; + } + + if (p->win) { + XCompositeUnredirectWindow(xdisp, p->win, + CompositeRedirectAutomatic); + XSelectInput(xdisp, p->win, 0); + p->win = 0; + } +} + +void XCompcapMain::updateSettings(obs_data_t settings) +{ + PLock lock(&p->lock); + XErrorLock xlock; + ObsGsContextHolder obsctx; + + blog(LOG_DEBUG, "Settings updating"); + + Window prevWin = p->win; + + xcc_cleanup(p); + + if (settings) { + const char *windowName = obs_data_getstring(settings, + "capture_window"); + + p->win = getWindowFromString(windowName); + + p->cut_top = obs_data_getint(settings, "cut_top"); + p->cut_left = obs_data_getint(settings, "cut_left"); + p->cut_right = obs_data_getint(settings, "cut_right"); + p->cut_bot = obs_data_getint(settings, "cut_bot"); + p->lockX = obs_data_getbool(settings, "lock_x"); + p->swapRedBlue = obs_data_getbool(settings, "swap_redblue"); + } else { + p->win = prevWin; + } + + xlock.resetError(); + + XCompositeRedirectWindow(xdisp, p->win, CompositeRedirectAutomatic); + + if (xlock.gotError()) { + blog(LOG_ERROR, "XCompositeRedirectWindow failed: %s", + xlock.getErrorText().c_str()); + return; + } + + XSelectInput(xdisp, p->win, StructureNotifyMask); + XSync(xdisp, 0); + + XWindowAttributes attr; + if (!XGetWindowAttributes(xdisp, p->win, &attr)) { + p->win = 0; + p->width = 0; + p->height = 0; + return; + } + + gs_color_format cf = GS_RGBA; + + p->width = attr.width; + p->height = attr.height; + + if (p->cut_top + p->cut_bot < (int)p->height) { + p->cur_cut_top = p->cut_top; + p->cur_cut_bot = p->cut_bot; + } else { + p->cur_cut_top = 0; + p->cur_cut_bot = 0; + } + + if (p->cut_left + p->cut_right < (int)p->width) { + p->cur_cut_left = p->cut_left; + p->cur_cut_right = p->cut_right; + } else { + p->cur_cut_left = 0; + p->cur_cut_right = 0; + } + + if (p->tex) + texture_destroy(p->tex); + + uint8_t *texData = new uint8_t[width() * height() * 4]; + + for (unsigned int i = 0; i < width() * height() * 4; i += 4) { + texData[i + 0] = p->swapRedBlue ? 0xFF : 0; + texData[i + 1] = 0; + texData[i + 2] = p->swapRedBlue ? 0 : 0xFF; + texData[i + 3] = 0xFF; + } + + const uint8_t* texDataArr[] = { texData, 0 }; + + p->tex = gs_create_texture(width(), height(), cf, 1, + (const void**)texDataArr, 0); + + delete[] texData; + + if (!p->swapRedBlue) { + GLuint tex = *(GLuint*)texture_getobj(p->tex); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE); + glBindTexture(GL_TEXTURE_2D, 0); + } + + const int attrs[] = + { + GLX_BIND_TO_TEXTURE_RGBA_EXT, GL_TRUE, + GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT, + GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT, + GLX_DOUBLEBUFFER, GL_FALSE, + None + }; + + int nelem = 0; + GLXFBConfig* configs = glXChooseFBConfig(xdisp, + XCompcap::getRootWindowScreen(attr.root), + attrs, &nelem); + + if (nelem <= 0) { + blog(LOG_ERROR, "no matching fb config found"); + p->win = 0; + p->height = 0; + p->width = 0; + return; + } + + glXGetFBConfigAttrib(xdisp, configs[0], GLX_Y_INVERTED_EXT, &nelem); + p->inverted = nelem != 0; + + xlock.resetError(); + + p->pixmap = XCompositeNameWindowPixmap(xdisp, p->win); + + if (xlock.gotError()) { + blog(LOG_ERROR, "XCompositeNameWindowPixmap failed: %s", + xlock.getErrorText().c_str()); + p->pixmap = 0; + XFree(configs); + return; + } + + const int attribs[] = + { + GLX_TEXTURE_TARGET_EXT, GLX_TEXTURE_2D_EXT, + GLX_TEXTURE_FORMAT_EXT, GLX_TEXTURE_FORMAT_RGBA_EXT, + None + }; + + p->glxpixmap = glXCreatePixmap(xdisp, configs[0], p->pixmap, attribs); + + if (xlock.gotError()) { + blog(LOG_ERROR, "glXCreatePixmap failed: %s", + xlock.getErrorText().c_str()); + XFreePixmap(xdisp, p->pixmap); + XFree(configs); + p->pixmap = 0; + p->glxpixmap = 0; + return; + } + + XFree(configs); + + p->gltex = gs_create_texture(p->width, p->height, cf, 1, 0, + GS_GL_DUMMYTEX); + + GLuint gltex = *(GLuint*)texture_getobj(p->gltex); + glBindTexture(GL_TEXTURE_2D, gltex); + glXBindTexImageEXT(xdisp, p->glxpixmap, GLX_FRONT_LEFT_EXT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); +} + +void XCompcapMain::tick(float seconds) +{ + UNUSED_PARAMETER(seconds); + + PLock lock(&p->lock, true); + + if (!lock.isLocked()) + return; + + XCompcap::processEvents(); + + if (XCompcap::windowWasReconfigured(p->win)) + updateSettings(0); + + if (!p->tex || !p->gltex) + return; + + gs_entercontext(obs_graphics()); + + if (p->lockX) { + XLockDisplay(xdisp); + XSync(xdisp, 0); + } + + gs_copy_texture_region(p->tex, 0, 0, p->gltex, p->cur_cut_left, + p->cur_cut_top, width(), height()); + + if (p->lockX) + XUnlockDisplay(xdisp); + + gs_leavecontext(); +} + +void XCompcapMain::render(effect_t effect) +{ + PLock lock(&p->lock, true); + + if (!lock.isLocked() || !p->tex) + return; + + eparam_t image = effect_getparambyname(effect, "image"); + effect_settexture(effect, image, p->tex); + + gs_enable_blending(false); + gs_draw_sprite(p->tex, 0, 0, 0); +} + +uint32_t XCompcapMain::width() +{ + return p->width - p->cur_cut_left - p->cur_cut_right; +} + +uint32_t XCompcapMain::height() +{ + return p->height - p->cur_cut_bot - p->cur_cut_top; +} diff --git a/plugins/linux-xcomposite/xcompcap-main.h b/plugins/linux-xcomposite/xcompcap-main.h new file mode 100644 index 000000000..8d231764a --- /dev/null +++ b/plugins/linux-xcomposite/xcompcap-main.h @@ -0,0 +1,27 @@ +#pragma once + +struct XCompcapMain_private; + +class XCompcapMain +{ + public: + static bool init(); + static void deinit(); + + static obs_properties_t properties(const char *locale); + static void defaults(obs_data_t settings); + + XCompcapMain(obs_data_t settings, obs_source_t source); + ~XCompcapMain(); + + void updateSettings(obs_data_t settings); + + void tick(float seconds); + void render(effect_t effect); + + uint32_t width(); + uint32_t height(); + + private: + XCompcapMain_private *p; +};